blob: f31a943ee84ffcdf61607b00273871f35eb8b95b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2015 David Green and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Green - initial API and implementation
* Torkild U. Resheim - Handle links when transforming, bug 325006
* Jeremie Bresson - Bug 492302
*******************************************************************************/
package org.eclipse.mylyn.wikitext.parser.builder;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.regex.Pattern;
import org.eclipse.mylyn.wikitext.parser.Attributes;
import org.eclipse.mylyn.wikitext.parser.ImageAttributes;
import org.eclipse.mylyn.wikitext.parser.ImageAttributes.Align;
import org.eclipse.mylyn.wikitext.parser.LinkAttributes;
import org.eclipse.mylyn.wikitext.parser.ListAttributes;
import org.eclipse.mylyn.wikitext.parser.QuoteAttributes;
import org.eclipse.mylyn.wikitext.parser.TableAttributes;
import org.eclipse.mylyn.wikitext.parser.TableCellAttributes;
import org.eclipse.mylyn.wikitext.parser.TableRowAttributes;
import org.eclipse.mylyn.wikitext.util.DefaultXmlStreamWriter;
import org.eclipse.mylyn.wikitext.util.FormattingXMLStreamWriter;
import org.eclipse.mylyn.wikitext.util.XmlStreamWriter;
import com.google.common.collect.ImmutableMap;
/**
* A builder that produces XHTML output. The nature of the output is affected by various settings on the builder.
*
* @author David Green
* @author Matthias Kempka extensibility improvements, see bug 259089
* @author Torkild U. Resheim
* @since 3.0
*/
public class HtmlDocumentBuilder extends AbstractXmlDocumentBuilder {
private static final Pattern ABSOLUTE_URL_PATTERN = Pattern.compile("[a-zA-Z]{3,8}://?.*"); //$NON-NLS-1$
private static final Map<SpanType, String> defaultSpanTypeToElementName;
static {
ImmutableMap.Builder<SpanType, String> spanTypeToElementNameBuilder = ImmutableMap.builder();
spanTypeToElementNameBuilder.put(SpanType.LINK, "a"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.BOLD, "b"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.CITATION, "cite"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.ITALIC, "i"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.EMPHASIS, "em"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.STRONG, "strong"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.DELETED, "del"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.INSERTED, "ins"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.QUOTE, "q"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.UNDERLINED, "u"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.SUPERSCRIPT, "sup"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.SUBSCRIPT, "sub"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.SPAN, "span"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.CODE, "code"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.MONOSPACE, "tt"); //$NON-NLS-1$
spanTypeToElementNameBuilder.put(SpanType.MARK, "mark"); //$NON-NLS-1$
defaultSpanTypeToElementName = spanTypeToElementNameBuilder.build();
}
private static final Map<BlockType, ElementInfo> blockTypeToElementInfo;
static {
ImmutableMap.Builder<BlockType, ElementInfo> blockTypeToElementInfoBuilder = ImmutableMap.builder();
blockTypeToElementInfoBuilder.put(BlockType.BULLETED_LIST, new ElementInfo("ul")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.CODE, new ElementInfo("pre", null, null, new ElementInfo("code"))); //$NON-NLS-1$ //$NON-NLS-2$
blockTypeToElementInfoBuilder.put(BlockType.DIV, new ElementInfo("div")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.FOOTNOTE, new ElementInfo("footnote")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.LIST_ITEM, new ElementInfo("li")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.NUMERIC_LIST, new ElementInfo("ol")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.DEFINITION_LIST, new ElementInfo("dl")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.DEFINITION_TERM, new ElementInfo("dt")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.DEFINITION_ITEM, new ElementInfo("dd")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.PARAGRAPH, new ElementInfo("p")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.PREFORMATTED, new ElementInfo("pre")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.QUOTE, new ElementInfo("blockquote")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.TABLE, new ElementInfo("table")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.TABLE_CELL_HEADER, new ElementInfo("th")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.TABLE_CELL_NORMAL, new ElementInfo("td")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.TABLE_ROW, new ElementInfo("tr")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.TIP, new ElementInfo("div", "tip", //$NON-NLS-1$ //$NON-NLS-2$
"border: 1px solid #090;background-color: #dfd;margin: 20px;padding: 0px 6px 0px 6px;")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.WARNING, new ElementInfo("div", "warning", //$NON-NLS-1$ //$NON-NLS-2$
"border: 1px solid #c00;background-color: #fcc;margin: 20px;padding: 0px 6px 0px 6px;")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.INFORMATION, new ElementInfo("div", "info", //$NON-NLS-1$ //$NON-NLS-2$
"border: 1px solid #3c78b5;background-color: #D8E4F1;margin: 20px;padding: 0px 6px 0px 6px;")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.NOTE, new ElementInfo("div", "note", //$NON-NLS-1$ //$NON-NLS-2$
"border: 1px solid #F0C000;background-color: #FFFFCE;margin: 20px;padding: 0px 6px 0px 6px;")); //$NON-NLS-1$
blockTypeToElementInfoBuilder.put(BlockType.PANEL, new ElementInfo("div", "panel", //$NON-NLS-1$ //$NON-NLS-2$
"border: 1px solid #ccc;background-color: #FFFFCE;margin: 10px;padding: 0px 6px 0px 6px;")); //$NON-NLS-1$
blockTypeToElementInfo = blockTypeToElementInfoBuilder.build();
}
private Map<SpanType, String> spanTypeToElementName = ImmutableMap.copyOf(defaultSpanTypeToElementName);
private String htmlNsUri = "http://www.w3.org/1999/xhtml"; //$NON-NLS-1$
private String htmlDtd = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; //$NON-NLS-1$
private boolean xhtmlStrict = false;
private boolean emitAsDocument = true;
private boolean emitDtd = false;
private String encoding = "utf-8"; //$NON-NLS-1$
private String title;
private String defaultAbsoluteLinkTarget;
private List<Stylesheet> stylesheets = null;
private boolean useInlineStyles = true;
private boolean suppressBuiltInStyles = false;
private String linkRel;
private String prependImagePrefix;
private boolean filterEntityReferences = false;
private String copyrightNotice;
private String htmlFilenameFormat = null;
private HtmlDocumentHandler documentHandler = new DefaultDocumentHandler();
private final Stack<ElementInfo> blockState = new Stack<ElementInfo>();
/**
* construct the HtmlDocumentBuilder.
*
* @param out
* the writer to which content is written
*/
public HtmlDocumentBuilder(Writer out) {
this(out, false);
}
/**
* construct the HtmlDocumentBuilder.
*
* @param out
* the writer to which content is written
* @param formatting
* indicate if the output should be formatted
*/
public HtmlDocumentBuilder(Writer out, boolean formatting) {
super(formatting ? createFormattingXmlStreamWriter(out) : new DefaultXmlStreamWriter(out));
}
/**
* construct the HtmlDocumentBuilder.
*
* @param writer
* the writer to which content is written
*/
public HtmlDocumentBuilder(XmlStreamWriter writer) {
super(writer);
}
/**
* Copy the configuration of this builder to the provided one. After calling this method the configuration of the
* other builder should be the same as this one, including stylesheets. Subclasses that have configurable settings
* should override this method to ensure that those settings are properly copied.
*
* @param other
* the builder to which settings are copied.
*/
public void copyConfiguration(HtmlDocumentBuilder other) {
other.setBase(getBase());
other.setBaseInHead(isBaseInHead());
other.setDefaultAbsoluteLinkTarget(getDefaultAbsoluteLinkTarget());
other.setEmitAsDocument(isEmitAsDocument());
other.setEmitDtd(isEmitDtd());
other.setHtmlDtd(getHtmlDtd());
other.setHtmlNsUri(getHtmlNsUri());
other.setLinkRel(getLinkRel());
other.setTitle(getTitle());
other.setUseInlineStyles(isUseInlineStyles());
other.setSuppressBuiltInStyles(isSuppressBuiltInStyles());
other.setXhtmlStrict(xhtmlStrict);
other.setPrependImagePrefix(prependImagePrefix);
other.setCopyrightNotice(getCopyrightNotice());
other.setHtmlFilenameFormat(htmlFilenameFormat);
other.spanTypeToElementName = spanTypeToElementName;
if (stylesheets != null) {
other.stylesheets = new ArrayList<Stylesheet>();
other.stylesheets.addAll(stylesheets);
}
}
protected static XmlStreamWriter createFormattingXmlStreamWriter(Writer out) {
return new FormattingXMLStreamWriter(new DefaultXmlStreamWriter(out)) {
@Override
protected boolean preserveWhitespace(String elementName) {
return elementName.equals("pre") || elementName.equals("code"); //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
/**
* Provides an element name for the given {@code spanType} replacing the previous mapping. The new
* {@code elementName} is used when the corresponding {@link SpanType} is {@link #beginSpan(SpanType, Attributes)
* started}.
*
* @param spanType
* the span type
* @param elementName
* the element name to use in the generated HTML when emitting spans of the given type
*/
public void setElementNameOfSpanType(SpanType spanType, String elementName) {
checkNotNull(spanType, "Must provide spanType"); //$NON-NLS-1$
checkNotNull(elementName, "Must provide elementName"); //$NON-NLS-1$
ImmutableMap.Builder<SpanType, String> builder = ImmutableMap.builder();
for (Entry<SpanType, String> entry : spanTypeToElementName.entrySet()) {
if (!entry.getKey().equals(spanType)) {
builder.put(entry);
}
}
builder.put(spanType, elementName);
spanTypeToElementName = builder.build();
}
/**
* 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>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;</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;
}
/**
* Specify the character encoding for use in the HTML meta tag. For example, if the charset is specified as
* <code>"utf-8"</code>: <code>&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"/&gt;</code> The
* default is <code>"utf-8"</code>. Ignored unless {@link #isEmitAsDocument()}
*/
public String getEncoding() {
return encoding;
}
/**
* Specify the character encoding for use in the HTML meta tag. For example, if the charset is specified as
* <code>"utf-8"</code>: <code>&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"/&gt;</code> The
* default is <code>"utf-8"</code>.
*
* @param encoding
* the character encoding to use, or null if the HTML meta tag should not be emitted Ignored unless
* {@link #isEmitAsDocument()}
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Set the document title, which will be emitted into the &lt;title&gt; 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 &lt;title&gt; element. Ignored unless
* {@link #isEmitAsDocument()}
*
* @param title
* the title or null if there is none
*/
public void setTitle(String title) {
this.title = title;
}
/**
* A default target attribute for links that have absolute (not relative) urls. By default this value is null.
* Setting this value will cause all HTML anchors to have their target attribute set if it's not explicitly
* specified in a {@link LinkAttributes}.
*/
public String getDefaultAbsoluteLinkTarget() {
return defaultAbsoluteLinkTarget;
}
/**
* A default target attribute for links that have absolute (not relative) urls. By default this value is null.
* Setting this value will cause all HTML anchors to have their target attribute set if it's not explicitly
* specified in a {@link LinkAttributes}.
*/
public void setDefaultAbsoluteLinkTarget(String defaultAbsoluteLinkTarget) {
this.defaultAbsoluteLinkTarget = defaultAbsoluteLinkTarget;
}
/**
* indicate if the builder should attempt to conform to strict XHTML rules. The default is false.
*/
public boolean isXhtmlStrict() {
return xhtmlStrict;
}
/**
* indicate if the builder should attempt to conform to strict XHTML rules. The default is false.
*/
public void setXhtmlStrict(boolean xhtmlStrict) {
this.xhtmlStrict = xhtmlStrict;
}
/**
* Add a CSS stylesheet to the output document as an URL, where the CSS stylesheet is referenced as an HTML link.
* Calling this method after {@link #beginDocument() starting the document} has no effect. Generates code similar to
* the following: <code>
* &lt;link type="text/css" rel="stylesheet" href="url"/>
* </code>
*
* @param url
* the CSS url to use, which may be relative or absolute
* @return the stylesheet, whose attributes may be modified
* @see #addCssStylesheet(File)
* @deprecated use {@link #addCssStylesheet(Stylesheet)} instead
*/
@Deprecated
public void addCssStylesheet(String url) {
addCssStylesheet(new Stylesheet(url));
}
/**
* Add a CSS stylesheet to the output document, where the contents of the CSS stylesheet are embedded in the HTML.
* Calling this method after {@link #beginDocument() starting the document} has no effect. Generates code similar to
* the following:
*
* <pre>
* &lt;code&gt;
* &lt;style type=&quot;text/css&quot;&gt;
* ... contents of the file ...
* &lt;/style&gt;
* &lt;/code&gt;
* </pre>
*
* @param file
* the CSS file whose contents must be available
* @return the stylesheet, whose attributes may be modified
* @see #addCssStylesheet(String)
* @deprecated use {@link #addCssStylesheet(Stylesheet)} instead
*/
@Deprecated
public void addCssStylesheet(File file) {
addCssStylesheet(new Stylesheet(file));
}
/**
* Add a CSS stylesheet to the output document. Calling this method after {@link #beginDocument() starting the
* document} has no effect.
*/
public void addCssStylesheet(Stylesheet stylesheet) {
if (stylesheet.file != null) {
checkFileReadable(stylesheet.file);
}
if (stylesheets == null) {
stylesheets = new ArrayList<Stylesheet>();
}
stylesheets.add(stylesheet);
}
protected void checkFileReadable(File file) {
if (!file.exists()) {
throw new IllegalArgumentException(MessageFormat.format(Messages.getString("HtmlDocumentBuilder.3"), file)); //$NON-NLS-1$
}
if (!file.isFile()) {
throw new IllegalArgumentException(MessageFormat.format(Messages.getString("HtmlDocumentBuilder.1"), file)); //$NON-NLS-1$
}
if (!file.canRead()) {
throw new IllegalArgumentException(MessageFormat.format(Messages.getString("HtmlDocumentBuilder.2"), file)); //$NON-NLS-1$
}
}
/**
* Indicate if inline styles should be used when creating output such as text boxes. When disabled inline styles are
* suppressed and CSS classes are used instead, with the default styles emitted as a stylesheet in the document
* head. If disabled and {@link #isEmitAsDocument()} is false, this option has the same effect as
* {@link #isSuppressBuiltInStyles()}. The default is true.
*
* @see #isSuppressBuiltInStyles()
*/
public boolean isUseInlineStyles() {
return useInlineStyles;
}
/**
* Indicate if inline styles should be used when creating output such as text boxes. When disabled inline styles are
* suppressed and CSS classes are used instead, with the default styles emitted as a stylesheet in the document
* head. If disabled and {@link #isEmitAsDocument()} is false, this option has the same effect as
* {@link #isSuppressBuiltInStyles()}. The default is true.
*/
public void setUseInlineStyles(boolean useInlineStyles) {
this.useInlineStyles = useInlineStyles;
}
/**
* indicate if default built-in CSS styles should be suppressed. Built-in styles are styles that are emitted by this
* builder to create the desired visual effect when rendering certain types of elements, such as warnings or infos.
* the default is false.
*
* @see #isUseInlineStyles()
*/
public boolean isSuppressBuiltInStyles() {
return suppressBuiltInStyles;
}
/**
* indicate if default built-in CSS styles should be suppressed. Built-in styles are styles that are emitted by this
* builder to create the desired visual effect when rendering certain types of elements, such as warnings or infos.
* the default is false.
*/
public void setSuppressBuiltInStyles(boolean suppressBuiltInStyles) {
this.suppressBuiltInStyles = suppressBuiltInStyles;
}
/**
* The 'rel' value for HTML links. If specified the value is applied to all links generated by the builder. The
* default value is null. Setting this value to "nofollow" is recommended for rendering HTML in areas where users
* may add links, for example in a blog comment. See
* <a href="http://en.wikipedia.org/wiki/Nofollow">http://en.wikipedia.org/wiki/Nofollow</a> for more information.
*
* @return the rel or null if there is none.
* @see LinkAttributes#getRel()
*/
public String getLinkRel() {
return linkRel;
}
/**
* The 'rel' value for HTML links. If specified the value is applied to all links generated by the builder. The
* default value is null. Setting this value to "nofollow" is recommended for rendering HTML in areas where users
* may add links, for example in a blog comment. See
* <a href="http://en.wikipedia.org/wiki/Nofollow">http://en.wikipedia.org/wiki/Nofollow</a> for more information.
*
* @param linkRel
* the rel or null if there is none.
* @see LinkAttributes#getRel()
*/
public void setLinkRel(String linkRel) {
this.linkRel = linkRel;
}
/**
* Provides an {@link HtmlDocumentHandler} for this builder.
*
* @param documentHandler
* the document handler
* @see HtmlDocumentHandler
*/
public void setDocumentHandler(HtmlDocumentHandler documentHandler) {
this.documentHandler = checkNotNull(documentHandler, "Must provide a documentHandler"); //$NON-NLS-1$
}
private class DefaultDocumentHandler implements HtmlDocumentHandler {
@Override
public void beginDocument(HtmlDocumentBuilder builder, XmlStreamWriter writer) {
if (emitAsDocument) {
if (encoding != null && encoding.length() > 0) {
writer.writeStartDocument(encoding, "1.0"); //$NON-NLS-1$
} else {
writer.writeStartDocument();
}
if (emitDtd && htmlDtd != null) {
writer.writeDTD(htmlDtd);
}
if (copyrightNotice != null) {
writer.writeComment(copyrightNotice);
}
writer.writeStartElement(htmlNsUri, "html"); //$NON-NLS-1$
writer.writeDefaultNamespace(htmlNsUri);
emitHead();
beginBody();
} else {
// sanity check
if (stylesheets != null && !stylesheets.isEmpty()) {
throw new IllegalStateException(Messages.getString("HtmlDocumentBuilder.0")); //$NON-NLS-1$
}
}
}
@Override
public void endDocument(HtmlDocumentBuilder builder, XmlStreamWriter writer) {
if (emitAsDocument) {
endBody();
writer.writeEndElement(); // html
writer.writeEndDocument();
}
}
}
@Override
public void beginDocument() {
writer.setDefaultNamespace(htmlNsUri);
documentHandler.beginDocument(this, writer);
}
/**
* Emit the HTML head, including the head tag itself.
*
* @see #emitHeadContents()
*/
protected void emitHead() {
writer.writeStartElement(htmlNsUri, "head"); //$NON-NLS-1$
emitHeadContents();
writer.writeEndElement(); // head
}
/**
* emit the contents of the HTML head, excluding the head tag itself. Subclasses may override to change the contents
* of the head. Subclasses should consider calling <code>super.emitHeadContents()</code> in order to preserve
* features such as emitting the base, title and stylesheets.
*
* @see #emitHead()
*/
protected void emitHeadContents() {
if (encoding != null && encoding.length() > 0) {
// bug 259786: add the charset as a HTML meta http-equiv
// see http://www.w3.org/International/tutorials/tutorial-char-enc/
//
// <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
writer.writeEmptyElement(htmlNsUri, "meta"); //$NON-NLS-1$
writer.writeAttribute("http-equiv", "Content-Type"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeAttribute("content", String.format("text/html; charset=%s", encoding)); //$NON-NLS-1$//$NON-NLS-2$
}
if (copyrightNotice != null) {
writer.writeEmptyElement(htmlNsUri, "meta"); //$NON-NLS-1$
writer.writeAttribute("name", "copyright"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeAttribute("content", copyrightNotice); //$NON-NLS-1$
}
if (base != null && baseInHead) {
writer.writeEmptyElement(htmlNsUri, "base"); //$NON-NLS-1$
writer.writeAttribute("href", base.toString()); //$NON-NLS-1$
}
if (title != null) {
writer.writeStartElement(htmlNsUri, "title"); //$NON-NLS-1$
writer.writeCharacters(title);
writer.writeEndElement(); // title
}
if (!useInlineStyles && !suppressBuiltInStyles) {
writer.writeStartElement(htmlNsUri, "style"); //$NON-NLS-1$
writer.writeAttribute("type", "text/css"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeCharacters("\n"); //$NON-NLS-1$
for (Entry<BlockType, ElementInfo> ent : blockTypeToElementInfo.entrySet()) {
ElementInfo elementInfo = ent.getValue();
while (elementInfo != null) {
if (elementInfo.cssStyles != null && elementInfo.cssClass != null) {
String[] classes = elementInfo.cssClass.split("\\s+"); //$NON-NLS-1$
for (String cssClass : classes) {
writer.writeCharacters("."); //$NON-NLS-1$
writer.writeCharacters(cssClass);
writer.writeCharacters(" "); //$NON-NLS-1$
}
writer.writeCharacters("{"); //$NON-NLS-1$
writer.writeCharacters(elementInfo.cssStyles);
writer.writeCharacters("}\n"); //$NON-NLS-1$
}
elementInfo = elementInfo.next;
}
}
writer.writeEndElement();
}
if (stylesheets != null) {
for (Stylesheet stylesheet : stylesheets) {
emitStylesheet(stylesheet);
}
}
}
private void emitStylesheet(Stylesheet stylesheet) {
if (stylesheet.url != null) {
// <link type="text/css" rel="stylesheet" href="url"/>
writer.writeEmptyElement(htmlNsUri, "link"); //$NON-NLS-1$
writer.writeAttribute("type", "text/css"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeAttribute("rel", "stylesheet"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeAttribute("href", makeUrlAbsolute(stylesheet.url)); //$NON-NLS-1$
for (Entry<String, String> attr : stylesheet.attributes.entrySet()) {
String attrName = attr.getKey();
if (!"type".equals(attrName) && !"rel".equals(attrName) && !"href".equals(attrName)) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
writer.writeAttribute(attrName, attr.getValue());
}
}
} else {
// <style type="text/css">
// ... contents of the file ...
// </style>
writer.writeStartElement(htmlNsUri, "style"); //$NON-NLS-1$
writer.writeAttribute("type", "text/css"); //$NON-NLS-1$ //$NON-NLS-2$
for (Entry<String, String> attr : stylesheet.attributes.entrySet()) {
String attrName = attr.getKey();
if (!"type".equals(attrName)) { //$NON-NLS-1$
writer.writeAttribute(attrName, attr.getValue());
}
}
String css;
if (stylesheet.file != null) {
try {
css = readFully(stylesheet.file);
} catch (IOException e) {
throw new IllegalStateException(MessageFormat.format(Messages.getString("HtmlDocumentBuilder.4"), //$NON-NLS-1$
stylesheet.file), e);
}
} else {
try {
css = readFully(stylesheet.reader, 1024);
} catch (IOException e) {
throw new IllegalStateException(Messages.getString("HtmlDocumentBuilder.5"), e); //$NON-NLS-1$
}
}
writer.writeCharacters(css);
writer.writeEndElement();
}
}
@Override
public void endDocument() {
documentHandler.endDocument(this, writer);
writer.close();
}
/**
* begin the body by emitting the body element. Overriding methods should call <code>super.beginBody()</code>.
*
* @see #endBody()
*/
protected void beginBody() {
writer.writeStartElement(htmlNsUri, "body"); //$NON-NLS-1$
}
/**
* end the body by emitting the body end element tag. Overriding methods should call <code>super.endBody()</code>.
*
* @see #beginBody()
*/
protected void endBody() {
writer.writeEndElement(); // body
}
@Override
public void entityReference(String entity) {
if (filterEntityReferences && !entity.isEmpty()) {
if (entity.charAt(0) == '#') {
writer.writeEntityRef(entity);
} else {
List<String> emitEntity = HtmlEntities.instance().nameToEntityReferences(entity);
if (emitEntity.isEmpty()) {
writer.writeCharacters("&"); //$NON-NLS-1$
writer.writeCharacters(entity);
writer.writeCharacters(";"); //$NON-NLS-1$
} else {
for (String numericEntity : emitEntity) {
writer.writeEntityRef(numericEntity);
}
}
}
} else {
writer.writeEntityRef(entity);
}
}
@Override
public void acronym(String text, String definition) {
writer.writeStartElement(htmlNsUri, "acronym"); //$NON-NLS-1$
writer.writeAttribute("title", definition); //$NON-NLS-1$
writer.writeCharacters(text);
writer.writeEndElement();
}
@Override
public void link(Attributes attributes, String hrefOrHashName, String text) {
writer.writeStartElement(htmlNsUri, spanTypeToElementName.get(SpanType.LINK));
emitAnchorHref(hrefOrHashName);
applyLinkAttributes(attributes, hrefOrHashName);
characters(text);
writer.writeEndElement();
}
@Override
public void beginBlock(BlockType type, Attributes attributes) {
ElementInfo elementInfo = blockTypeToElementInfo.get(type);
if (elementInfo == null) {
throw new IllegalStateException(type.name());
}
writeBlockElements(attributes, elementInfo);
blockState.push(elementInfo);
if (type == BlockType.TABLE) {
applyTableAttributes(attributes);
} else if (type == BlockType.TABLE_ROW) {
applyTableRowAttributes(attributes);
} else if (type == BlockType.TABLE_CELL_HEADER || type == BlockType.TABLE_CELL_NORMAL) {
applyCellAttributes(attributes);
} else if (type == BlockType.BULLETED_LIST || type == BlockType.NUMERIC_LIST) {
applyListAttributes(attributes);
} else if (type == BlockType.QUOTE) {
applyQuoteAttributes(attributes);
} else {
applyAttributes(attributes);
// create the titled panel effect if a title is specified
if (attributes.getTitle() != null) {
beginBlock(BlockType.PARAGRAPH, new Attributes());
beginSpan(SpanType.BOLD, new Attributes());
characters(attributes.getTitle());
endSpan();
endBlock();
}
}
}
private void writeBlockElements(Attributes attributes, ElementInfo elementInfo) {
writer.writeStartElement(htmlNsUri, elementInfo.name);
String originalCssClasses = attributes.getCssClass();
if (elementInfo.cssClass != null) {
attributes.appendCssClass(elementInfo.cssClass);
}
if (useInlineStyles && !suppressBuiltInStyles && elementInfo.cssStyles != null) {
attributes.appendCssStyle(elementInfo.cssStyles);
}
if (elementInfo.next != null) {
if (originalCssClasses != null) {
writer.writeAttribute("class", originalCssClasses); //$NON-NLS-1$
}
Attributes childAttributes = new Attributes();
childAttributes.setCssClass(originalCssClasses);
writeBlockElements(childAttributes, elementInfo.next);
}
}
@Override
public void beginHeading(int level, Attributes attributes) {
if (level > 6) {
level = 6;
}
writer.writeStartElement(htmlNsUri, "h" + level); //$NON-NLS-1$
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);
if (type == SpanType.LINK && attributes instanceof LinkAttributes) {
String href = ((LinkAttributes) attributes).getHref();
emitAnchorHref(href);
applyLinkAttributes(attributes, href);
} else {
applyAttributes(attributes);
}
}
@Override
public void endBlock() {
ElementInfo elementInfo = blockState.pop();
for (int x = 0; x < elementInfo.size(); ++x) {
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"); //$NON-NLS-1$
applyImageAttributes(attributes);
url = prependImageUrl(url);
writer.writeAttribute("src", makeUrlAbsolute(url)); //$NON-NLS-1$
}
private void applyListAttributes(Attributes attributes) {
applyAttributes(attributes);
if (attributes instanceof ListAttributes) {
ListAttributes listAttributes = (ListAttributes) attributes;
if (listAttributes.getStart() != null) {
writer.writeAttribute("start", listAttributes.getStart()); //$NON-NLS-1$
}
}
}
private void applyQuoteAttributes(Attributes attributes) {
applyAttributes(attributes);
if (attributes instanceof QuoteAttributes) {
QuoteAttributes quoteAttributes = (QuoteAttributes) attributes;
if (quoteAttributes.getCitation() != null) {
writer.writeAttribute("cite", quoteAttributes.getCitation()); //$NON-NLS-1$
}
}
}
private void applyTableAttributes(Attributes attributes) {
applyAttributes(attributes);
if (attributes.getTitle() != null) {
writer.writeAttribute("title", attributes.getTitle()); //$NON-NLS-1$
}
if (attributes instanceof TableAttributes) {
TableAttributes tableAttributes = (TableAttributes) attributes;
if (tableAttributes.getBgcolor() != null) {
writer.writeAttribute("bgcolor", tableAttributes.getBgcolor()); //$NON-NLS-1$
}
if (tableAttributes.getBorder() != null) {
writer.writeAttribute("border", tableAttributes.getBorder()); //$NON-NLS-1$
}
if (tableAttributes.getCellpadding() != null) {
writer.writeAttribute("cellpadding", tableAttributes.getCellpadding()); //$NON-NLS-1$
}
if (tableAttributes.getCellspacing() != null) {
writer.writeAttribute("cellspacing", tableAttributes.getCellspacing()); //$NON-NLS-1$
}
if (tableAttributes.getFrame() != null) {
writer.writeAttribute("frame", tableAttributes.getFrame()); //$NON-NLS-1$
}
if (tableAttributes.getRules() != null) {
writer.writeAttribute("rules", tableAttributes.getRules()); //$NON-NLS-1$
}
if (tableAttributes.getSummary() != null) {
writer.writeAttribute("summary", tableAttributes.getSummary()); //$NON-NLS-1$
}
if (tableAttributes.getWidth() != null) {
writer.writeAttribute("width", tableAttributes.getWidth()); //$NON-NLS-1$
}
}
}
private void applyTableRowAttributes(Attributes attributes) {
applyAttributes(attributes);
if (attributes.getTitle() != null) {
writer.writeAttribute("title", attributes.getTitle()); //$NON-NLS-1$
}
if (attributes instanceof TableRowAttributes) {
TableRowAttributes tableRowAttributes = (TableRowAttributes) attributes;
if (tableRowAttributes.getBgcolor() != null) {
writer.writeAttribute("bgcolor", tableRowAttributes.getBgcolor()); //$NON-NLS-1$
}
if (tableRowAttributes.getAlign() != null) {
writer.writeAttribute("align", tableRowAttributes.getAlign()); //$NON-NLS-1$
}
if (tableRowAttributes.getValign() != null) {
writer.writeAttribute("valign", tableRowAttributes.getValign()); //$NON-NLS-1$
}
}
}
private void applyCellAttributes(Attributes attributes) {
applyAttributes(attributes);
if (attributes.getTitle() != null) {
writer.writeAttribute("title", attributes.getTitle()); //$NON-NLS-1$
}
if (attributes instanceof TableCellAttributes) {
TableCellAttributes tableCellAttributes = (TableCellAttributes) attributes;
if (tableCellAttributes.getBgcolor() != null) {
writer.writeAttribute("bgcolor", tableCellAttributes.getBgcolor()); //$NON-NLS-1$
}
if (tableCellAttributes.getAlign() != null) {
writer.writeAttribute("align", tableCellAttributes.getAlign()); //$NON-NLS-1$
}
if (tableCellAttributes.getValign() != null) {
writer.writeAttribute("valign", tableCellAttributes.getValign()); //$NON-NLS-1$
}
if (tableCellAttributes.getRowspan() != null) {
writer.writeAttribute("rowspan", tableCellAttributes.getRowspan()); //$NON-NLS-1$
}
if (tableCellAttributes.getColspan() != null) {
writer.writeAttribute("colspan", tableCellAttributes.getColspan()); //$NON-NLS-1$
}
}
}
private void applyImageAttributes(Attributes attributes) {
int border = 0;
Align align = null;
if (attributes instanceof ImageAttributes) {
ImageAttributes imageAttributes = (ImageAttributes) attributes;
border = imageAttributes.getBorder();
align = imageAttributes.getAlign();
}
if (xhtmlStrict) {
String borderStyle = String.format("border-width: %spx;", border); //$NON-NLS-1$
String alignStyle = null;
if (align != null) {
switch (align) {
case Center:
case Right:
case Left:
alignStyle = "text-align: " + align.name().toLowerCase() + ";"; //$NON-NLS-1$ //$NON-NLS-2$
break;
case Bottom:
case Baseline:
case Top:
case Middle:
alignStyle = "vertical-align: " + align.name().toLowerCase() + ";"; //$NON-NLS-1$ //$NON-NLS-2$
break;
case Texttop:
alignStyle = "vertical-align: text-top;"; //$NON-NLS-1$
break;
case Absmiddle:
alignStyle = "vertical-align: middle;"; //$NON-NLS-1$
break;
case Absbottom:
alignStyle = "vertical-align: bottom;"; //$NON-NLS-1$
break;
}
}
String additionalStyles = borderStyle;
if (alignStyle != null) {
additionalStyles += alignStyle;
}
if (attributes.getCssStyle() == null || attributes.getCssStyle().length() == 0) {
attributes.setCssStyle(additionalStyles);
} else {
attributes.setCssStyle(additionalStyles + attributes.getCssStyle());
}
}
applyAttributes(attributes);
boolean haveAlt = false;
if (attributes instanceof ImageAttributes) {
ImageAttributes imageAttributes = (ImageAttributes) attributes;
if (imageAttributes.getHeight() != -1) {
String val = Integer.toString(imageAttributes.getHeight());
if (imageAttributes.isHeightPercentage()) {
val += "%"; //$NON-NLS-1$
}
writer.writeAttribute("height", val); //$NON-NLS-1$
}
if (imageAttributes.getWidth() != -1) {
String val = Integer.toString(imageAttributes.getWidth());
if (imageAttributes.isWidthPercentage()) {
val += "%"; //$NON-NLS-1$
}
writer.writeAttribute("width", val); //$NON-NLS-1$
}
if (!xhtmlStrict && align != null) {
writer.writeAttribute("align", align.name().toLowerCase()); //$NON-NLS-1$
}
if (imageAttributes.getAlt() != null) {
haveAlt = true;
writer.writeAttribute("alt", imageAttributes.getAlt()); //$NON-NLS-1$
}
}
if (attributes.getTitle() != null) {
writer.writeAttribute("title", attributes.getTitle()); //$NON-NLS-1$
if (!haveAlt) {
haveAlt = true;
writer.writeAttribute("alt", attributes.getTitle()); //$NON-NLS-1$
}
}
if (xhtmlStrict) {
if (!haveAlt) {
// XHTML requires img/@alt
writer.writeAttribute("alt", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
} else {
// only specify border attribute if it's not already specified in CSS
writer.writeAttribute("border", Integer.toString(border)); //$NON-NLS-1$
}
}
private void applyLinkAttributes(Attributes attributes, String href) {
applyAttributes(attributes);
boolean hasTarget = false;
String rel = linkRel;
if (attributes instanceof LinkAttributes) {
LinkAttributes linkAttributes = (LinkAttributes) attributes;
if (linkAttributes.getTarget() != null) {
hasTarget = true;
writer.writeAttribute("target", linkAttributes.getTarget()); //$NON-NLS-1$
}
if (linkAttributes.getRel() != null) {
rel = rel == null ? linkAttributes.getRel() : linkAttributes.getRel() + ' ' + rel;
}
}
if (attributes.getTitle() != null && attributes.getTitle().length() > 0) {
writer.writeAttribute("title", attributes.getTitle()); //$NON-NLS-1$
}
if (!hasTarget && defaultAbsoluteLinkTarget != null && href != null) {
if (isExternalLink(href)) {
writer.writeAttribute("target", defaultAbsoluteLinkTarget); //$NON-NLS-1$
}
}
if (rel != null) {
writer.writeAttribute("rel", rel); //$NON-NLS-1$
}
}
/**
* Note: this method does not apply the {@link Attributes#getTitle() title}.
*/
private void applyAttributes(Attributes attributes) {
if (attributes.getId() != null) {
writer.writeAttribute("id", attributes.getId()); //$NON-NLS-1$
}
if (attributes.getCssClass() != null) {
writer.writeAttribute("class", attributes.getCssClass()); //$NON-NLS-1$
}
if (attributes.getCssStyle() != null) {
writer.writeAttribute("style", attributes.getCssStyle()); //$NON-NLS-1$
}
if (attributes.getLanguage() != null) {
writer.writeAttribute("lang", attributes.getLanguage()); //$NON-NLS-1$
}
}
@Override
public void imageLink(Attributes linkAttributes, Attributes imageAttributes, String href, String imageUrl) {
writer.writeStartElement(htmlNsUri, "a"); //$NON-NLS-1$
emitAnchorHref(href);
applyLinkAttributes(linkAttributes, href);
writer.writeEmptyElement(htmlNsUri, "img"); //$NON-NLS-1$
applyImageAttributes(imageAttributes);
imageUrl = prependImageUrl(imageUrl);
writer.writeAttribute("src", makeUrlAbsolute(imageUrl)); //$NON-NLS-1$
writer.writeEndElement(); // a
}
/**
* emit the href attribute of an anchor. Subclasses may override to alter the default href or to add other
* attributes such as <code>onclick</code>. Overriding classes should pass the href to
* {@link #makeUrlAbsolute(String)} prior to writing it to the writer.
*
* @param href
* the url for the href attribute
* @see #getHtmlFilenameFormat()
*/
protected void emitAnchorHref(String href) {
if (href != null) {
writer.writeAttribute("href", makeUrlAbsolute(applyHtmlFilenameFormat(href))); //$NON-NLS-1$
}
}
/**
* Applies the {@link #getHtmlFilenameFormat() HTML filename format} to links that are missing a filename extension
* using the format specified by {@link #getHtmlFilenameFormat()}.
*
* @param href
* the link
* @return the given {@code href} with the {@link #getHtmlFilenameFormat() HTML filename format} applied, or the
* original {@code href} if the {@link #getHtmlFilenameFormat()} is null
* @see #getHtmlFilenameFormat()
*/
private String applyHtmlFilenameFormat(String href) {
if (getHtmlFilenameFormat() != null) {
if (isMissingFilenameExtension(href) && !isAbsoluteUrl(href)) {
int indexOfHash = href.indexOf('#');
if (indexOfHash > 0) {
href = getHtmlFilenameFormat().replace("$1", href.substring(0, indexOfHash)) //$NON-NLS-1$
+ href.substring(indexOfHash);
} else if (indexOfHash == -1) {
href = getHtmlFilenameFormat().replace("$1", href); //$NON-NLS-1$
}
}
}
return href;
}
private boolean isAbsoluteUrl(String href) {
return ABSOLUTE_URL_PATTERN.matcher(href).matches();
}
/**
* Determines whether or not the {@code href} has a a filename extension
*
* @param href
* the reference to test
* @return {@code true} if the {@code href} is relative and missing a filename extension, otherwise {@code false}
*/
private boolean isMissingFilenameExtension(String href) {
int lasIndexOfSlash = href.lastIndexOf('/');
return href.lastIndexOf('.') <= lasIndexOfSlash && lasIndexOfSlash < href.length() - 1;
}
/**
* Provides the HTML filename format which is used to rewrite relative URLs having no filename extension. Specifying
* the HTML filename format enables content to have relative hyperlinks to generated files without having to specify
* the filename extension in the hyperlink. If specified, the returned value is a pattern where "$1" indicates the
* location of the filename. For example "$1.html". The default value is {@code null}.
*
* @see #setHtmlFilenameFormat(String)
* @return the HTML filename format or {@code null}
*/
public String getHtmlFilenameFormat() {
return htmlFilenameFormat;
}
/**
* Sets the HTML filename format which is used to rewrite relative URLs having no filename extension. Specifying the
* HTML filename format enables content to have relative hyperlinks to generated files without having to specify the
* filename extension in the hyperlink. If specified, the returned value is a pattern where "$1" indicates the
* location of the filename. For example "$1.html". The default value is {@code null}.
*
* @param htmlFilenameFormat
* the HTML filename format or <code>null</code>
* @see #getHtmlFilenameFormat()
*/
public void setHtmlFilenameFormat(String htmlFilenameFormat) {
checkArgument(htmlFilenameFormat == null || htmlFilenameFormat.contains("$1"), //$NON-NLS-1$
"The HTML filename format must contain \"$1\""); //$NON-NLS-1$
this.htmlFilenameFormat = htmlFilenameFormat;
}
private String prependImageUrl(String imageUrl) {
if (prependImagePrefix == null || prependImagePrefix.length() == 0) {
return imageUrl;
}
if (isAbsoluteUrl(imageUrl) || imageUrl.contains("../")) { //$NON-NLS-1$
return imageUrl;
}
String url = prependImagePrefix;
if (!prependImagePrefix.endsWith("/")) { //$NON-NLS-1$
url += '/';
}
url += imageUrl;
return url;
}
@Override
public void lineBreak() {
writer.writeEmptyElement(htmlNsUri, "br"); //$NON-NLS-1$
}
/**
*
*/
@Override
public void horizontalRule() {
writer.writeEmptyElement(htmlNsUri, "hr"); //$NON-NLS-1$
}
@Override
public void charactersUnescaped(String literal) {
writer.writeLiteral(literal);
}
private static final class ElementInfo {
final String name;
final String cssClass;
final String cssStyles;
final ElementInfo next;
public ElementInfo(String name, String cssClass, String cssStyles) {
this(name, cssClass, cssStyles, null);
}
public ElementInfo(String name, String cssClass, String cssStyles, ElementInfo next) {
this.name = name;
this.cssClass = cssClass;
this.cssStyles = cssStyles != null && !cssStyles.endsWith(";") ? cssStyles + ';' : cssStyles; //$NON-NLS-1$
this.next = next;
}
public ElementInfo(String name) {
this(name, null, null);
}
public int size() {
return 1 + (next == null ? 0 : next.size());
}
}
/**
* A CSS stylesheet definition, created via one of {@link HtmlDocumentBuilder#addCssStylesheet(File)} or
* {@link HtmlDocumentBuilder#addCssStylesheet(String)}.
*/
public static class Stylesheet {
private final String url;
private final File file;
private final Reader reader;
private final Map<String, String> attributes = new HashMap<String, String>();
/**
* Create a CSS stylesheet where the contents of the CSS stylesheet are embedded in the HTML. Generates code
* similar to the following:
*
* <pre>
* &lt;code&gt;
* &lt;style type=&quot;text/css&quot;&gt;
* ... contents of the file ...
* &lt;/style&gt;
* &lt;/code&gt;
* </pre>
*
* @param file
* the CSS file whose contents must be available
*/
public Stylesheet(File file) {
if (file == null) {
throw new IllegalArgumentException();
}
this.file = file;
url = null;
reader = null;
}
/**
* Create a CSS stylesheet to the output document as an URL where the CSS stylesheet is referenced as an HTML
* link. Calling this method after {@link #beginDocument() starting the document} has no effect. Generates code
* similar to the following:
*
* <pre>
* &lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;url&quot;/&gt;
* </pre>
*
* @param url
* the CSS url to use, which may be relative or absolute
*/
public Stylesheet(String url) {
if (url == null || url.length() == 0) {
throw new IllegalArgumentException();
}
this.url = url;
file = null;
reader = null;
}
/**
* Create a CSS stylesheet where the contents of the CSS stylesheet are embedded in the HTML. Generates code
* similar to the following:
*
* <pre>
* &lt;code&gt;
* &lt;style type=&quot;text/css&quot;&gt;
* ... contents of the file ...
* &lt;/style&gt;
* &lt;/code&gt;
* </pre>
*
* The caller is responsible for closing the reader.
*
* @param reader
* the reader from which content is provided.
*/
public Stylesheet(Reader reader) {
if (reader == null) {
throw new IllegalArgumentException();
}
this.reader = reader;
file = null;
url = null;
}
/**
* the attributes of the stylesheet, which may be modified prior to adding to the document. Attributes
* <code>href</code>, <code>type</code> and <code>rel</code> are all ignored.
*/
public Map<String, String> getAttributes() {
return attributes;
}
/**
* the file of the stylesheet, or null if it's not defined
*/
public File getFile() {
return file;
}
/**
* the url of the stylesheet, or null if it's not defined
*/
public String getUrl() {
return url;
}
/**
* the content reader, or null if it's not defined.
*/
public Reader getReader() {
return reader;
}
}
private String readFully(File inputFile) throws IOException {
int length = (int) inputFile.length();
if (length <= 0) {
length = 2048;
}
return readFully(getReader(inputFile), length);
}
private String readFully(Reader input, int bufferSize) throws IOException {
StringBuilder buf = new StringBuilder(bufferSize);
try {
Reader reader = new BufferedReader(input);
int c;
while ((c = reader.read()) != -1) {
buf.append((char) c);
}
} finally {
input.close();
}
return buf.toString();
}
protected Reader getReader(File inputFile) throws FileNotFoundException {
return new FileReader(inputFile);
}
/**
* if specified, the prefix is prepended to relative image urls.
*/
public void setPrependImagePrefix(String prependImagePrefix) {
this.prependImagePrefix = prependImagePrefix;
}
/**
* if specified, the prefix is prepended to relative image urls.
*/
public String getPrependImagePrefix() {
return prependImagePrefix;
}
/**
* Indicates that {@link #entityReference(String) entity references} should be filtered. Defaults to false. When
* filtered, known HTML entity references are converted to their numeric counterpart, and unknown entity references
* are emitted as plain text.
*
* @see <a href="http://www.w3schools.com/tags/ref_entities.asp">HTML Entity Reference</a>
*/
public boolean isFilterEntityReferences() {
return filterEntityReferences;
}
/**
* Indicates that {@link #entityReference(String) entity references} should be filtered. Defaults to false. When
* filtered, known HTML entity references are converted to their numeric counterpart, and unknown entity references
* are emitted as plain text.
*
* @see <a href="http://www.w3schools.com/tags/ref_entities.asp">HTML Entity Reference</a>
*/
public void setFilterEntityReferences(boolean filterEntityReferences) {
this.filterEntityReferences = filterEntityReferences;
}
/**
* the copyright notice that should appear in the generated output
*/
public String getCopyrightNotice() {
return copyrightNotice;
}
/**
* the copyright notice that should appear in the generated output
*
* @param copyrightNotice
* the notice, or null if there should be none
*/
public void setCopyrightNotice(String copyrightNotice) {
this.copyrightNotice = copyrightNotice;
}
}