/*******************************************************************************
* Copyright (c) 2007, 2009 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
* Peter Stibrany - bug 294383
*******************************************************************************/
package com.datastax.wikitext.anttask;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.eclipse.mylyn.internal.wikitext.core.parser.builder.DefaultSplittingStrategy;
import org.eclipse.mylyn.internal.wikitext.core.parser.builder.NoSplittingStrategy;
import org.eclipse.mylyn.internal.wikitext.core.parser.builder.SplitOutlineItem;
import org.eclipse.mylyn.internal.wikitext.core.parser.builder.SplittingHtmlDocumentBuilder;
import org.eclipse.mylyn.internal.wikitext.core.parser.builder.SplittingOutlineParser;
import org.eclipse.mylyn.internal.wikitext.core.parser.builder.SplittingStrategy;
import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder;
import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage;
/**
* An Ant task for converting lightweight markup to HTML format.
*
* @author David Green
* @since 1.0
*/
public class MarkupToHtmlTask extends MarkupTask {
private final List<FileSet> filesets = new ArrayList<FileSet>();
protected String htmlFilenameFormat = "$1.html"; //$NON-NLS-1$
protected boolean overwrite = true;
private final List<Stylesheet> stylesheets = new ArrayList<Stylesheet>();
protected File file;
protected String title;
protected String linkRel;
protected boolean multipleOutputFiles = false;
protected boolean formatOutput = false;
protected boolean navigationImages = false;
protected String prependImagePrefix = null;
private boolean useInlineCssStyles = true;
private boolean suppressBuiltInCssStyles = false;
private String defaultAbsoluteLinkTarget;
private boolean xhtmlStrict = false;
private boolean emitDoctype = true;
private String htmlDoctype = null;
@Override
public void execute() throws BuildException {
if (file == null && filesets.isEmpty()) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.1")); //$NON-NLS-1$
}
if (file != null && !filesets.isEmpty()) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.2")); //$NON-NLS-1$
}
if (file != null) {
if (!file.exists()) {
throw new BuildException(MessageFormat.format(Messages.getString("MarkupToHtmlTask.3"), file)); //$NON-NLS-1$
} else if (!file.isFile()) {
throw new BuildException(MessageFormat.format(Messages.getString("MarkupToHtmlTask.4"), file)); //$NON-NLS-1$
} else if (!file.canRead()) {
throw new BuildException(MessageFormat.format(Messages.getString("MarkupToHtmlTask.5"), file)); //$NON-NLS-1$
}
}
MarkupLanguage markupLanguage = createMarkupLanguage();
for (Stylesheet stylesheet : stylesheets) {
if (stylesheet.url == null && stylesheet.file == null) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.6")); //$NON-NLS-1$
}
if (stylesheet.url != null && stylesheet.file != null) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.7")); //$NON-NLS-1$
}
if (stylesheet.file != null) {
if (!stylesheet.file.exists()) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.8") + stylesheet.file); //$NON-NLS-1$
}
if (!stylesheet.file.isFile()) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.9") + stylesheet.file); //$NON-NLS-1$
}
if (!stylesheet.file.canRead()) {
throw new BuildException(Messages.getString("MarkupToHtmlTask.10") + stylesheet.file); //$NON-NLS-1$
}
}
}
Set<File> outputFolders = new HashSet<File>();
for (FileSet fileset : filesets) {
File filesetBaseDir = fileset.getDir(getProject());
DirectoryScanner ds = fileset.getDirectoryScanner(getProject());
String[] files = ds.getIncludedFiles();
if (files != null) {
File baseDir = ds.getBasedir();
for (String file : files) {
File inputFile = new File(baseDir, file);
testForOutputFolderConflict(outputFolders, inputFile);
try {
processFile(markupLanguage, filesetBaseDir, inputFile);
} catch (BuildException e) {
throw e;
} catch (Exception e) {
throw new BuildException(MessageFormat.format(
Messages.getString("MarkupToHtmlTask.11"), inputFile, //$NON-NLS-1$
e.getMessage()), e);
}
}
}
}
if (file != null) {
testForOutputFolderConflict(outputFolders, file);
try {
processFile(markupLanguage, file.getParentFile(), file);
} catch (BuildException e) {
throw e;
} catch (Exception e) {
throw new BuildException(MessageFormat.format(
Messages.getString("MarkupToHtmlTask.12"), file, e.getMessage()), e); //$NON-NLS-1$
}
}
}
private void testForOutputFolderConflict(Set<File> outputFolders, File inputFile) {
if (multipleOutputFiles) {
File outputFolder = inputFile.getAbsoluteFile().getParentFile();
if (!outputFolders.add(outputFolder)) {
log(MessageFormat.format(Messages.getString("MarkupToHtmlTask.13"), outputFolder), Project.MSG_WARN); //$NON-NLS-1$
}
}
}
/**
* process the file
*
* @param baseDir
* @param source
* @return the lightweight markup, or null if the file was not written
* @throws BuildException
*/
protected String processFile(MarkupLanguage markupLanguage, final File baseDir, final File source)
throws BuildException {
log(MessageFormat.format(Messages.getString("MarkupToHtmlTask.14"), source), Project.MSG_VERBOSE); //$NON-NLS-1$
String markupContent = null;
String name = source.getName();
if (name.lastIndexOf('.') != -1) {
name = name.substring(0, name.lastIndexOf('.'));
}
File htmlOutputFile = computeHtmlFile(source, name);
if (!htmlOutputFile.exists() || overwrite || htmlOutputFile.lastModified() < source.lastModified()) {
if (markupContent == null) {
markupContent = readFully(source);
}
performValidation(source, markupContent);
Writer writer;
try {
writer = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(htmlOutputFile)), "utf-8"); //$NON-NLS-1$
} catch (Exception e) {
throw new BuildException(MessageFormat.format(
Messages.getString("MarkupToHtmlTask.16"), htmlOutputFile, e.getMessage()), e); //$NON-NLS-1$
}
try {
HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer, formatOutput);
for (Stylesheet stylesheet : stylesheets) {
HtmlDocumentBuilder.Stylesheet builderStylesheet;
if (stylesheet.url != null) {
builderStylesheet = new HtmlDocumentBuilder.Stylesheet(stylesheet.url);
} else {
builderStylesheet = new HtmlDocumentBuilder.Stylesheet(stylesheet.file);
}
builder.addCssStylesheet(builderStylesheet);
if (!stylesheet.attributes.isEmpty()) {
for (Map.Entry<String, String> attr : stylesheet.attributes.entrySet()) {
builderStylesheet.getAttributes().put(attr.getKey(), attr.getValue());
}
}
}
builder.setTitle(title == null ? name : title);
builder.setEmitDtd(emitDoctype);
if (emitDoctype && htmlDoctype != null) {
builder.setHtmlDtd(htmlDoctype);
}
builder.setUseInlineStyles(useInlineCssStyles);
builder.setSuppressBuiltInStyles(suppressBuiltInCssStyles);
builder.setLinkRel(linkRel);
builder.setDefaultAbsoluteLinkTarget(defaultAbsoluteLinkTarget);
builder.setPrependImagePrefix(prependImagePrefix);
builder.setXhtmlStrict(xhtmlStrict);
SplittingStrategy splittingStrategy = multipleOutputFiles ? new DefaultSplittingStrategy()
: new NoSplittingStrategy();
SplittingOutlineParser outlineParser = new SplittingOutlineParser();
outlineParser.setMarkupLanguage(markupLanguage.clone());
outlineParser.setSplittingStrategy(splittingStrategy);
SplitOutlineItem item = outlineParser.parse(markupContent);
item.setSplitTarget(htmlOutputFile.getName());
SplittingHtmlDocumentBuilder splittingBuilder = new SplittingHtmlDocumentBuilder();
splittingBuilder.setRootBuilder(builder);
splittingBuilder.setOutline(item);
splittingBuilder.setRootFile(htmlOutputFile);
splittingBuilder.setNavigationImages(navigationImages);
splittingBuilder.setFormatting(formatOutput);
MarkupParser parser = new MarkupParser();
parser.setMarkupLanguage(markupLanguage);
parser.setBuilder(splittingBuilder);
parser.parse(markupContent);
processed(markupContent, item, baseDir, source);
} finally {
try {
writer.close();
} catch (Exception e) {
throw new BuildException(MessageFormat.format(
Messages.getString("MarkupToHtmlTask.17"), htmlOutputFile, //$NON-NLS-1$
e.getMessage()), e);
}
}
}
return markupContent;
}
void processed(String markupContent, SplitOutlineItem item, final File baseDir, final File source) {
}
protected File computeHtmlFile(final File source, String name) {
return new File(source.getParentFile(), htmlFilenameFormat.replace("$1", name)); //$NON-NLS-1$
}
/**
* @see #setHtmlFilenameFormat(String)
*/
public String getHtmlFilenameFormat() {
return htmlFilenameFormat;
}
/**
* The format of the HTML output file. Consists of a pattern where the '$1' is replaced with the filename of the
* input file. Default value is <code>$1.html</code>
*
* @param htmlFilenameFormat
*/
public void setHtmlFilenameFormat(String htmlFilenameFormat) {
this.htmlFilenameFormat = htmlFilenameFormat;
}
/**
* The document title, as it appears in the head
*/
public String getTitle() {
return title;
}
/**
* The document title, as it appears in the head
*/
public void setTitle(String title) {
this.title = title;
}
/**
* the file to process
*/
public File getFile() {
return file;
}
/**
* the file to process
*/
public void setFile(File file) {
this.file = file;
}
/**
* Adds a set of files to process.
*/
public void addFileset(FileSet set) {
filesets.add(set);
}
public void addStylesheet(Stylesheet stylesheet) {
if (stylesheet == null) {
throw new IllegalArgumentException();
}
stylesheets.add(stylesheet);
}
/**
* indicate if output should be generated to multiple output files.
*/
public boolean isMultipleOutputFiles() {
return multipleOutputFiles;
}
/**
* indicate if output should be generated to multiple output files.
*/
public void setMultipleOutputFiles(boolean multipleOutputFiles) {
this.multipleOutputFiles = multipleOutputFiles;
}
/**
* indicate if the output should be formatted
*/
public boolean isFormatOutput() {
return formatOutput;
}
/**
* indicate if the output should be formatted
*/
public void setFormatOutput(boolean formatOutput) {
this.formatOutput = formatOutput;
}
/**
* indicate if navigation links should be images
*/
public boolean isNavigationImages() {
return navigationImages;
}
/**
* indicate if navigation links should be images
*/
public void setNavigationImages(boolean navigationImages) {
this.navigationImages = navigationImages;
}
/**
* @see HtmlDocumentBuilder#isUseInlineStyles()
*/
public boolean isUseInlineCssStyles() {
return useInlineCssStyles;
}
/**
* @see HtmlDocumentBuilder#isUseInlineStyles()
*/
public void setUseInlineCssStyles(boolean useInlineCssStyles) {
this.useInlineCssStyles = useInlineCssStyles;
}
/**
* @see HtmlDocumentBuilder#isSuppressBuiltInStyles()
*/
public boolean isSuppressBuiltInCssStyles() {
return suppressBuiltInCssStyles;
}
/**
* @see HtmlDocumentBuilder#isSuppressBuiltInStyles()
*/
public void setSuppressBuiltInCssStyles(boolean suppressBuiltInCssStyles) {
this.suppressBuiltInCssStyles = suppressBuiltInCssStyles;
}
public static class Attribute {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public static class Stylesheet {
private File file;
private String url;
private final Map<String, String> attributes = new HashMap<String, String>();
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public void addConfiguredAttribute(Attribute attribute) {
attributes.put(attribute.getName(), attribute.getValue());
}
}
/**
* 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.
*/
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.
*/
public void setLinkRel(String linkRel) {
this.linkRel = linkRel;
}
public String getPrependImagePrefix() {
return prependImagePrefix;
}
public void setPrependImagePrefix(String prependImagePrefix) {
this.prependImagePrefix = prependImagePrefix;
}
/**
* indicate if target files should be overwritten even if their timestamps are newer than the source files.
*/
public boolean isOverwrite() {
return overwrite;
}
/**
* indicate if target files should be overwritten even if their timestamps are newer than the source files.
*/
public void setOverwrite(boolean overwrite) {
this.overwrite = overwrite;
}
/**
* 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.
*
* @since 1.1
*/
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.
*
* @since 1.1
*/
public void setDefaultAbsoluteLinkTarget(String defaultAbsoluteLinkTarget) {
this.defaultAbsoluteLinkTarget = defaultAbsoluteLinkTarget;
}
/**
* Indicate if the builder should attempt to conform to strict XHTML rules. The default is false.
*
* @see HtmlDocumentBuilder#isXhtmlStrict()
* @since 1.3
*/
public boolean isXhtmlStrict() {
return xhtmlStrict;
}
/**
* Indicate if the builder should attempt to conform to strict XHTML rules. The default is false.
*
* @see HtmlDocumentBuilder#isXhtmlStrict()
* @since 1.3
*/
public void setXhtmlStrict(boolean xhtmlStrict) {
this.xhtmlStrict = xhtmlStrict;
}
/**
* Indicate if the builder should emit DOCTYPE declaration. Default is true.
*
* @see HtmlDocumentBuilder#isEmitDtd()
* @since 1.3
*/
public boolean getEmitDoctype() {
return emitDoctype;
}
/**
* Indicate if the builder should emit DOCTYPE declaration. Default is true.
*
* @see HtmlDocumentBuilder#isEmitDtd()
* @since 1.3
*/
public void setEmitDoctype(boolean emitDtd) {
this.emitDoctype = emitDtd;
}
/**
* The DTD to use in the output document. Ignored if {@link #getEmitDoctype() emitDoctype} is false.
*
* @see HtmlDocumentBuilder#isEmitDtd()
* @return the DTD to use, or null if the default DTD should be used
* @since 1.3
*/
public String getHtmlDoctype() {
return htmlDoctype;
}
/**
* The DTD to use in the output document. Ignored if {@link #getEmitDoctype() emitDoctype} is false. The doctype
* should take the form: <code><!DOCTYPE html ...></code>
*
* @param htmlDoctype
* the DTD to use, or null if the default DTD should be used
* @since 1.3
*/
public void setHtmlDoctype(String htmlDoctype) {
this.htmlDoctype = htmlDoctype;
}
}