/**
* Copyright 2015-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.roboconf.doc.generator.internal.renderers;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import net.roboconf.core.model.beans.ApplicationTemplate;
import net.roboconf.core.utils.Utils;
import net.roboconf.doc.generator.DocConstants;
import net.roboconf.doc.generator.internal.AbstractStructuredRenderer;
/**
* A renderer that outputs HTML files.
* @author Vincent Zurczak - Linagora
*/
public class HtmlRenderer extends AbstractStructuredRenderer {
private static final String TITLE_MARKUP = "${TITLE}";
private static final String MENU_MARKUP = "${MENU}";
private static final String CONTENT_MARKUP = "${CONTENT}";
private static final String CSS_MARKUP = "${CSS}";
private String menu;
private final Map<String,StringBuilder> sectionNameToContent = new HashMap<> ();
/**
* Constructor.
* @param outputDirectory
* @param applicationTemplate
* @param applicationDirectory
* @param typeAnnotations
*/
public HtmlRenderer(
File outputDirectory,
ApplicationTemplate applicationTemplate,
File applicationDirectory,
Map<String,String> typeAnnotations ) {
super( outputDirectory, applicationTemplate, applicationDirectory, typeAnnotations );
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderTitle1(java.lang.String)
*/
@Override
protected String renderTitle1( String title ) {
return "<h1 id=\"" + createId( title ) + "\">" + title + "</h1>\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderTitle2(java.lang.String)
*/
@Override
protected String renderTitle2( String title ) {
return "<h2 id=\"" + createId( title ) + "\">" + title + "</h2>\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderTitle3(java.lang.String)
*/
@Override
protected String renderTitle3( String title ) {
return "<h3>" + title + "</h3>\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderParagraph(java.lang.String)
*/
@Override
protected String renderParagraph( String paragraph ) {
StringBuilder sb = new StringBuilder();
for( String s : paragraph.trim().split( "\n\n" )) {
sb.append( "\n<p>" );
sb.append( s.trim().replaceAll( "\n", "<br />" ));
sb.append( "</p>\n\n" );
}
return sb.toString();
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderList(java.util.Collection)
*/
@Override
protected String renderList( Collection<String> listItems ) {
StringBuilder sb = new StringBuilder();
sb.append( "\n<ul>\n" );
for( String s : listItems ) {
sb.append( "\t<li>" );
sb.append( s );
sb.append( "</li>\n" );
}
sb.append( "</ul>\n\n" );
return sb.toString();
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #startSection(java.lang.String)
*/
@Override
protected StringBuilder startSection( String sectionName ) {
return new StringBuilder();
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #endSection(java.lang.String, java.lang.StringBuilder)
*/
@Override
protected StringBuilder endSection( String sectionName, StringBuilder sb ) {
StringBuilder result = sb;
if( this.options.containsKey( DocConstants.OPTION_HTML_EXPLODED )) {
this.sectionNameToContent.put( sectionName, sb );
result = new StringBuilder();
} else {
result.append( "<p class=\"separator\"> </p>\n" );
}
return result;
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderSections(java.util.List)
*/
@Override
protected String renderSections( List<String> sectionNames ) {
StringBuilder sb = new StringBuilder();
if( this.options.containsKey( DocConstants.OPTION_HTML_EXPLODED )) {
if( sectionNames.size() > 0 ) {
sb.append( "<ul>\n" );
for( String sectionName : sectionNames ) {
int index = sectionName.lastIndexOf( '/' );
String title = sectionName.substring( index + 1 );
sb.append( "<li><a href=\"" );
sb.append( sectionName.replace( " ", "%20" ));
sb.append( ".html\">" );
sb.append( title );
sb.append( "</a></li>\n" );
}
sb.append( "</ul>\n" );
}
} else {
sb.append( "\n<p class=\"separator\"> </p>\n" );
}
return sb.toString();
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderPageBreak()
*/
@Override
protected String renderPageBreak() {
return "";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderImage(java.lang.String, net.roboconf.doc.generator.internal.AbstractStructuredRenderer.DiagramType, java.lang.String)
*/
@Override
protected String renderImage( String componentName, DiagramType type, String relativeImagePath ) {
String alt = componentName + " - " + type;
return "<img src=\"" + relativeImagePath + "\" alt=\"" + alt + "\" />\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderDocumentTitle()
*/
@Override
protected String renderDocumentTitle() {
return "<h1 id=\"" + this.messages.get( "introduction" ) + "\">" + this.applicationTemplate.getName() + "</h1>\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #renderDocumentIndex()
*/
@Override
protected String renderDocumentIndex() {
// What keys should we inject in the index?
List<String> keys = new ArrayList<> ();
keys.add( "introduction" );
keys.add( "components" );
if( this.options.containsKey( DocConstants.OPTION_RECIPE )) {
if( ! this.applicationTemplate.getGraphs().getFacetNameToFacet().isEmpty())
keys.add( "facets" );
} else {
keys.add( "instances" );
}
// Create the index
StringBuilder sb = new StringBuilder();
sb.append( "<ul>\n" );
for( String key : keys ) {
sb.append( "\t<li><a href=\"index.html#" );
sb.append( this.messages.get( key ).toLowerCase());
sb.append( "\">" );
sb.append( this.messages.get( key ));
sb.append( "</a></li>\n" );
}
sb.append( "</ul>\n" );
this.menu = sb.toString();
return "";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #applyBoldStyle(java.lang.String, java.lang.String)
*/
@Override
protected String applyBoldStyle( String text, String keyword ) {
return text.replaceAll( Pattern.quote( keyword ), "<b>" + keyword + "</b>" );
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #applyLink(java.lang.String, java.lang.String)
*/
@Override
protected String applyLink( String text, String linkId ) {
String link;
if( this.options.containsKey( DocConstants.OPTION_HTML_EXPLODED ))
link = "components/" + linkId + ".html".replace( " ", "%20" );
else
link = "#" + createId( linkId );
return text.replaceAll( Pattern.quote( text ), "<a href=\"" + link + "\">" + text + "</a>" );
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #startTable()
*/
@Override
protected String startTable() {
return "<table>\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #endTable()
*/
@Override
protected String endTable() {
return "</table>\n";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #addTableHeader(java.util.List)
*/
@Override
protected String addTableHeader( String... headerEntries ) {
StringBuilder sb = new StringBuilder();
sb.append( "<tr>\n" );
for( String s : headerEntries ) {
sb.append( "\t<th>" );
sb.append( s );
sb.append( "</th>\n" );
}
sb.append( "</tr>\n" );
return sb.toString();
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #addTableLine(java.util.List)
*/
@Override
protected String addTableLine( String... lineEntries ) {
StringBuilder sb = new StringBuilder();
sb.append( "<tr>\n" );
for( String s : lineEntries ) {
sb.append( "\t<td>" );
sb.append( s );
sb.append( "</td>\n" );
}
sb.append( "</tr>\n" );
return sb.toString();
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #indent()
*/
@Override
protected String indent() {
return " ";
}
/*
* (non-Javadoc)
* @see net.roboconf.doc.generator.internal.AbstractStructuredRenderer
* #writeFileContent(java.lang.String)
*/
@Override
protected File writeFileContent( String fileContent ) throws IOException {
// Load the template
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = getClass().getResourceAsStream( "/html.tpl" );
Utils.copyStreamSafely( in, out );
// Create the target directory
File targetFile = new File( this.outputDirectory, "index.html" );
Utils.createDirectory( targetFile.getParentFile());
// Deal with the CSS file
final String css;
final String cssReference = this.options.get( DocConstants.OPTION_HTML_CSS_REFERENCE );
if( cssReference != null ) {
css = cssReference.trim();
} else {
css = "style.css";
writeCssFile();
}
// Write sections
for( Map.Entry<String,StringBuilder> entry : this.sectionNameToContent.entrySet()) {
int index = entry.getKey().lastIndexOf( '/' );
String title = entry.getKey().substring( index + 1 );
String toWrite = out.toString( "UTF-8" )
.replace( TITLE_MARKUP, title )
.replace( CONTENT_MARKUP, entry.getValue().toString())
.replace( MENU_MARKUP, this.menu )
.replace( "href=\"", "href=\"../" )
.replace( "src=\"", "src=\"../" )
.replaceAll( "\n{3,}", "\n\n" );
if( cssReference != null )
toWrite = toWrite.replace( "../" + CSS_MARKUP, css );
else
toWrite = toWrite.replace( CSS_MARKUP, css );
File sectionFile = new File( this.outputDirectory, entry.getKey() + ".html" );
Utils.createDirectory( sectionFile.getParentFile());
Utils.writeStringInto( toWrite, sectionFile );
}
// Write the main file
String toWrite = out.toString( "UTF-8" )
.replace( TITLE_MARKUP, this.applicationTemplate.getName())
.replace( CSS_MARKUP, css )
.replace( CONTENT_MARKUP, fileContent )
.replace( MENU_MARKUP, this.menu )
.replaceAll( "\n{3,}", "\n\n" );
Utils.writeStringInto( toWrite, targetFile );
// And the header image
String imagePath = this.options.get( DocConstants.OPTION_HTML_HEADER_IMAGE_FILE );
try {
File sourceFile = null;
if( imagePath != null )
sourceFile = new File( imagePath );
if( sourceFile != null && sourceFile.exists())
in = new FileInputStream( sourceFile );
else
in = getClass().getResourceAsStream( "/roboconf.jpg" );
File imgFile = new File( this.outputDirectory, "header.jpg" );
Utils.copyStream( in, imgFile );
} finally {
Utils.closeQuietly( in );
}
return targetFile;
}
private String createId( String title ) {
return title.toLowerCase().replaceAll( "\\s+", "-" );
}
private void writeCssFile() throws IOException {
InputStream in = null;
String location = this.options.get( DocConstants.OPTION_HTML_CSS_FILE );
try {
if( ! Utils.isEmptyOrWhitespaces( location ))
in = new FileInputStream( new File( location ));
else
in = getClass().getResourceAsStream( "/style.css" );
File cssFile = new File( this.outputDirectory, "style.css" );
Utils.copyStream( in, cssFile );
} finally {
Utils.closeQuietly( in );
}
}
}