/* * Copyright 2012 James Moger * * 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 org.moxie; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.StringWriter; import java.nio.charset.Charset; import java.text.MessageFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import org.eclipse.mylyn.wikitext.confluence.core.ConfluenceLanguage; 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; import org.eclipse.mylyn.wikitext.mediawiki.core.MediaWikiLanguage; import org.eclipse.mylyn.wikitext.textile.core.TextileLanguage; import org.eclipse.mylyn.wikitext.tracwiki.core.TracWikiLanguage; import org.eclipse.mylyn.wikitext.twiki.core.TWikiLanguage; import org.moxie.maxml.Maxml; import org.moxie.maxml.MaxmlException; import org.moxie.maxml.MaxmlMap; import org.moxie.utils.FileUtils; import org.moxie.utils.MarkdownUtils; import org.moxie.utils.StringUtils; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.TemplateException; /** * Builds a web site or deployment documentation from Markdown source files. * * @author James Moger * */ public class Docs { private static PrintStream out = System.out; public static void execute(Build build, Doc doc, boolean verbose) { List<DocElement> revisedElements = new ArrayList<DocElement>(); // prepare report links for (DocElement element : doc.structure.elements) { if (element instanceof DocReport) { // switch to menu DocMenu reportMenu = new DocMenu(); // project report DocPage projectReport = reportMenu.createPage(); projectReport.setName("project data"); projectReport.setContent(new ProjectReport().report(build)); projectReport.setSrc("moxie-project.html"); // dependency report DocPage depReport = reportMenu.createPage(); depReport.setName("dependencies"); depReport.setContent(new DependencyReport().report(build)); depReport.setSrc("moxie-dependencies.html"); File testOutput = new File(build.getConfig().getReportsTargetDirectory(), "tests"); File coverageOutput = new File(build.getConfig().getReportsTargetDirectory(), "coverage"); boolean hasTests = testOutput.exists(); boolean hasCoverage = coverageOutput.exists(); if (hasTests || hasCoverage) { reportMenu.createDivider(); if (hasTests) { // test report try { FileUtils.copy(new File(doc.outputDirectory, "tests"), testOutput); } catch (IOException e) { build.getConsole().error(e, "failed to copy test report"); } DocPage testReport = reportMenu.createPage(); testReport.setName("unit tests"); testReport.setContent(iframe("tests/index.html")); testReport.setSrc("moxie-tests.html"); } if (hasCoverage) { // code coverage report try { FileUtils.copy(new File(doc.outputDirectory, "coverage"), coverageOutput); } catch (IOException e) { build.getConsole().error(e, "failed to copy coverage report"); } DocPage coverageReport = reportMenu.createPage(); coverageReport.setName("code coverage"); coverageReport.setContent(iframe("coverage/index.html")); coverageReport.setSrc("moxie-coverage.html"); } } // add reports menu revisedElements.add(reportMenu); } else { // do not alter revisedElements.add(element); } } doc.structure.elements = revisedElements; if (verbose) { doc.describe(build.getConsole()); } if (verbose) { build.getConsole().separator(); } generatePages(build, doc, verbose); } private static String iframe(String src) { return MessageFormat.format("<iframe src=\"{0}\" style=\"border:1px solid #ccc;\" width=\"100%\" height=\"90%\"></iframe>", src); } private static void generatePages(Build build, Doc doc, boolean verbose) { String projectName = build.getPom().name; if (StringUtils.isEmpty(projectName) && !StringUtils.isEmpty(doc.name)) { projectName = doc.name; } List<DocElement> allElements = new ArrayList<DocElement>(); for (DocElement element : doc.structure.elements) { allElements.add(element); if (element instanceof DocMenu) { for (DocElement subElement : ((DocMenu) element).elements) { if (subElement instanceof DocMenu) { // add all submenu elements allElements.addAll(((DocMenu) subElement).elements); } else { allElements.add(subElement); } } } } String header = generateHeader(projectName, build.getConfig().getProjectConfig(), doc); String footer = generateFooter(doc); if (!doc.structure.elements.isEmpty()) { build.getConsole().log("Generating Structured Documentation from source files... "); } // read references if (doc.references == null) { doc.references = new References(); } else { String content = FileUtils.readContent(new File(doc.sourceDirectory, doc.references.src), "\n"); doc.references.content = "\n\n" + content; } for (DocElement element : allElements) { if (!(element instanceof DocPage)) { // nothing to generate continue; } DocPage page = (DocPage) element; try { String fileName = getHref(page); if (StringUtils.isEmpty(page.src)) { // template page build.getConsole().log(1, "{0} => {1}", page.templates.get(0).src, fileName); } else { // markdown page build.getConsole().log(1, "{0} => {1}", page.src, fileName); } String content; List<Section> sections = new ArrayList<Section>(); String pager = ""; if (page.content != null) { // generated content content = page.content; processTemplates(doc, page); for (Substitute sub : doc.substitutions) { if (page.processSubstitutions || sub.isTemplate) { content = content.replace(sub.token, sub.value.toString()); } } for (Regex regex : doc.regexes) { content = content.replaceAll(regex.searchPattern, regex.replacePattern); } } else if (page.src.endsWith(".html") || page.src.endsWith(".htm")) { // static html content content = FileUtils.readContent(new File(doc.sourceDirectory, page.src), "\n"); } else { // begin markdown String markdownContent = FileUtils.readContent(new File(doc.sourceDirectory, page.src), "\n"); // append references, if specified if (!StringUtils.isEmpty(doc.references.content)) { markdownContent += doc.references.content; } Map<String, String> nomarkdownMap = new HashMap<String, String>(); // extract sections marked as no-markdown int nmd = 0; for (NoMarkdown nomarkdown : doc.nomarkdowns) { List<String> lines = Arrays.asList(markdownContent.split("\n")); StringBuilder strippedContent = new StringBuilder(); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (line.trim().startsWith(nomarkdown.startToken)) { // found start token, look for end token int beginCode = i + 1; int endCode = beginCode; for (int j = beginCode; j < lines.size(); j++) { String endLine = lines.get(j); if (endLine.trim().startsWith(nomarkdown.endToken)) { endCode = j; break; } } if (endCode > beginCode) { // append a placeholder for extracted content String nomarkdownKey = "%NOMARKDOWN" + nmd + "%"; strippedContent.append(nomarkdownKey).append( '\n'); nmd++; // build the hunk from lines StringBuilder sb = new StringBuilder(); for (String nl : lines.subList(beginCode, endCode)) { sb.append(nl).append('\n'); } String hunk = sb.toString(); if (nomarkdown instanceof ExcludeText) { // exclude this hunk from the output html nomarkdownMap.put(nomarkdownKey, ""); } else if (nomarkdown instanceof WikiText) { // convert this hunk to html from a wiki format StringWriter writer = new StringWriter(); HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer); // avoid the <html> and <body> tags builder.setEmitAsDocument(false); WikiText wikitext = (WikiText) nomarkdown; MarkupLanguage lang; switch (wikitext.syntax){ case TWIKI: lang = new TWikiLanguage(); break; case TEXTILE: lang = new TextileLanguage(); break; case TRACWIKI: lang = new TracWikiLanguage(); break; case MEDIAWIKI: lang = new MediaWikiLanguage(); break; case CONFLUENCE: lang = new ConfluenceLanguage(); break; default: throw new MoxieException("Unrecognized wiki syntax!"); } MarkupParser parser = new MarkupParser(lang); parser.setBuilder(builder); parser.parse(hunk); String htmlContent = writer.toString(); nomarkdownMap.put(nomarkdownKey, htmlContent); } else if (nomarkdown.prettify) { // // Syntax highlighting // StringBuilder ppclass = new StringBuilder(); ppclass.append("prettyprint"); if (nomarkdown.linenums) { ppclass.append(" linenums"); } if (!StringUtils.isEmpty(nomarkdown.lang)) { ppclass.append(" "); ppclass.append(nomarkdown.lang); } StringBuilder code = new StringBuilder(); code.append(MessageFormat.format("<pre class=''{0}''>\n", ppclass.toString())); code.append(StringUtils.escapeForHtml(hunk, false)); code.append("</pre>"); nomarkdownMap.put(nomarkdownKey, code.toString()); } else if (nomarkdown.escape) { // // escape the hunk, optionally wrap with pre // StringBuilder code = new StringBuilder(); if (nomarkdown.pre) { code.append("<pre>"); } String val = StringUtils.escapeForHtml(hunk, false); if (!nomarkdown.pre) { val = StringUtils.breakLinesForHtml(val); } code.append(val); if (nomarkdown.pre) { code.append("</pre>"); } nomarkdownMap.put(nomarkdownKey, code.toString()); } else { // // leave the hunk as-is // nomarkdownMap.put(nomarkdownKey, hunk); } // advance the i counter to endCode so we do not // include the lines within the hunk i = endCode; } else { // could not find closing token strippedContent.append(line); strippedContent.append('\n'); } } else { // regular line strippedContent.append(line); strippedContent.append('\n'); } } // replace markdown content with the stripped content markdownContent = strippedContent.toString(); } // prev/next pager links if (page.showPager) { String prev; if (page.prevPage == null) { prev = ""; } else { prev = MessageFormat.format("<li class=\"previous\"><a href=\"{0}\">← {1}</a></li>", getHref(page.prevPage), page.prevPage.name); } String next; if (page.nextPage == null) { next = ""; } else { next = MessageFormat.format("<li class=\"next\"><a href=\"{0}\">{1} →</a></li>", getHref(page.nextPage), page.nextPage.name); } String divClass = ""; String pagerClass = ""; if (!StringUtils.isEmpty(page.pagerLayout)) { if ("right".equals(page.pagerLayout)) { divClass = "class=\"pull-right\""; pagerClass = "class=\"pager\""; } else if ("justified".equals(page.pagerLayout)) { pagerClass = "class=\"pager\""; } } pager = MessageFormat.format("<div {0}><ul {1}>{2} {3}</ul></div>", divClass, pagerClass, prev, next); } // header links AtomicInteger sectionCounter = new AtomicInteger(); StringBuilder sb = new StringBuilder(); for (String line : Arrays.asList(markdownContent.split("\n"))) { if (line.length() == 0) { sb.append('\n'); continue; } if (line.charAt(0) == '#') { String section = line.substring(0, line.indexOf(' ')); String name = line.substring(line.indexOf(' ') + 1) .trim(); if (name.endsWith(section)) { name = name.substring(0, name.indexOf(section)) .trim(); } String h = "h" + section.length(); String id = "H" + sectionCounter.addAndGet(1); sections.add(new Section(id, name)); if (page.showHeaderLinks) { sb.append(MessageFormat.format("<{0} class=\"section\" id=''{2}''><a href=\"#{2}\" class=\"sectionlink\"><i class=\"icon-share-alt\"> </i></a>{1}</{0}>\n", h, name, id)); } else { sb.append(MessageFormat.format("<{0} id=''{2}''>{1}</{0}>\n", h, name, id)); } } else { // preserve line sb.append(line); sb.append('\n'); } } // replace markdown content with the navigable content markdownContent = sb.toString(); // transform markdown to html content = transformMarkdown(markdownContent.toString()); // reinsert nomarkdown chunks for (Map.Entry<String, String> nomarkdown : nomarkdownMap .entrySet()) { content = content.replaceFirst(nomarkdown.getKey(), Matcher.quoteReplacement(nomarkdown.getValue())); } // templates processTemplates(doc, page); for (Substitute sub : doc.substitutions) { if (page.processSubstitutions || sub.isTemplate) { content = content.replace(sub.token, sub.value.toString()); } } for (Regex regex : doc.regexes) { content = content.replaceAll(regex.searchPattern, regex.replacePattern); } for (Prop prop : doc.props) { String loadedContent = generatePropertiesContent(prop); content = content.replace(prop.token, loadedContent); } for (Load load : doc.loads) { String loadedContent = FileUtils.readContent(new File( load.file), "\n"); loadedContent = StringUtils.escapeForHtml(loadedContent, false); loadedContent = StringUtils.breakLinesForHtml(loadedContent); content = content.replace(load.token, loadedContent); } // end markdown } // Create the topbar links for this page String links = createLinks(page, doc.structure.elements); if (!StringUtils.isEmpty(doc.googlePlusId)) { links += "<li><a href='https://plus.google.com/" + doc.googlePlusId + "?prsrc=3' class='gpluspage'><img src='https://ssl.gstatic.com/images/icons/gplus-16.png' width='16' height='16 style='order: 0;'/></a></li>"; } // add Google+1 link if (doc.googlePlusOne && !StringUtils.isEmpty(build.getPom().url)) { links += "<li><div class='gplusone'><g:plusone size='small' href='" + build.getPom().url + "'></g:plusone></div></li>"; } String linksHtml = readResource(doc, "links.html"); linksHtml = linksHtml.replace("%PROJECTNAME%", projectName); linksHtml = linksHtml.replace("%PROJECTLINKS%", links); if (doc.logo != null) { linksHtml = linksHtml.replace("%PROJECTLOGO%", doc.logo.getName()); } links = linksHtml; // write final document OutputStreamWriter writer = new OutputStreamWriter( new FileOutputStream(new File(doc.outputDirectory, fileName)), Charset.forName("UTF-8")); writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<html>\n<head>\n"); writer.write(header); writer.append("\n<script src=\"./prettify/prettify.js\"></script>"); writer.append("\n<script src=\"./bootstrap/js/jquery.js\"></script>"); writer.append("\n<script src=\"./bootstrap/js/bootstrap.min.js\"></script>"); writer.write("\n</head>"); writer.write("\n<body onload='prettyPrint()'>"); writer.write(links); if (page.showToc) { if (page.isFluidLayout) { writer.write("\n<div class='container-fluid'>"); writer.write("\n<div class='row-fluid'>"); } else { writer.write("\n<div class='container'>"); writer.write("\n<div class='row'>"); } writer.write("\n<!-- sidebar -->"); writer.write("\n<div class='span3'>"); writer.write("\n<div class='well sidebar-nav'>"); writer.write("\n<ul class='nav nav-list'>"); writer.write("\n<li class='nav-header'>Table of Contents</li>"); for (Section section : sections) { writer.write(MessageFormat.format("\n<li><a href=''#{0}''>{1}</a></li>\n", section.id, section.name)); } writer.write("\n</ul>"); writer.write("\n</div>"); writer.write("\n</div>"); writer.write("\n<div class='span9'>"); } else { if (page.isFluidLayout) { writer.write("\n<div class='container-fluid'>"); } else { writer.write("\n<div class='container'>"); } } boolean manualPagerPlacement = false; // replace %PAGER% if (content.contains("%PAGER%")) { manualPagerPlacement = true; content = content.replace("%PAGER%", pager); } if (!manualPagerPlacement) { if (!StringUtils.isEmpty(page.pagerPlacement) && page.pagerPlacement.contains("top")) { // top pager writer.write(pager); } } writer.write("\n<!-- Begin Markdown -->\n"); writer.write(content); writer.write("\n<!-- End Markdown -->\n"); if (!manualPagerPlacement) { if (!StringUtils.isEmpty(page.pagerPlacement) && page.pagerPlacement.contains("bottom")) { // bottom pager writer.write(pager); } } writer.write("<footer class=\"footer\">"); writer.write(footer); writer.write("\n</footer>\n</div>"); if (page.showToc) { writer.write("\n</div></div>"); } if (!StringUtils.isEmpty(doc.googleAnalyticsId)) { String analytics = readResource(doc, "analytics.html"); analytics = analytics.replace("%ANALYTICSID%", doc.googleAnalyticsId); writer.append('\n'); writer.append(analytics); writer.append('\n'); } writer.write("\n</body>"); writer.write("\n</html>"); writer.close(); } catch (Throwable t) { build.getConsole().error(t, "Failed to transform " + page.src); } } // process pages which are not part of the normal structure if (!doc.freeformPages.isEmpty()) { build.getConsole().log("Generating content from Freemarker templates..."); for (DocPage page : doc.freeformPages) { try { // template pages String fileName = getHref(page); build.getConsole().log(1, "{0} => {1}", page.templates.get(0).src, fileName); String content = page.content; processTemplates(doc, page); for (Substitute sub : doc.substitutions) { if (page.processSubstitutions || sub.isTemplate) { content = content.replace(sub.token, sub.value.toString()); } } for (Regex regex : doc.regexes) { content = content.replaceAll(regex.searchPattern, regex.replacePattern); } File output = new File(doc.outputDirectory, page.as); FileUtils.writeContent(output, content); } catch (Throwable t) { build.getConsole().error(t, "Failed to transform " + page.src); } } } } static String extractResource(Doc doc, String folder, String resource) { String content = ""; try { InputStream is = doc.getClass().getResourceAsStream("/" + resource); ByteArrayOutputStream os = new ByteArrayOutputStream(); byte[] buffer = new byte[32767]; int len = 0; while ((len = is.read(buffer)) > -1) { os.write(buffer, 0, len); } content = os.toString("UTF-8"); os.close(); is.close(); File outputFile; if (StringUtils.isEmpty(folder)) { outputFile = new File(doc.outputDirectory, resource); } else { outputFile = new File(new File(doc.outputDirectory, folder), resource); } FileUtils.writeContent(outputFile, content); } catch (Exception e) { e.printStackTrace(); } return content; } static String readResource(Doc doc, String resource) { String content = ""; try { InputStream is = doc.getClass().getResourceAsStream("/" + resource); ByteArrayOutputStream os = new ByteArrayOutputStream(); byte[] buffer = new byte[32767]; int len = 0; while ((len = is.read(buffer)) > -1) { os.write(buffer, 0, len); } content = os.toString("UTF-8"); os.close(); is.close(); } catch (Exception e) { e.printStackTrace(); } return content; } static String readContent(File file) { String content = ""; if (file.exists()) { content = FileUtils.readContent(file, "\n"); } return content; } private static String createLinks(DocElement currentElement, List<DocElement> elements) { String linkPattern = "<li><a href=''{0}''>{1}</a></li>\n"; String currentLinkPattern = "<li class=''active''><a href=''{0}''>{1}</a></li>\n"; StringBuilder sb = new StringBuilder(); for (DocElement element : elements) { if (element instanceof DocPage) { DocPage page = (DocPage) element; if (!page.showNavbarLink) { continue; } String href = getHref(page); boolean active = currentElement.equals(page); if (active) { // current page sb.append(MessageFormat.format(currentLinkPattern, href, page.name)); } else { // page link sb.append(MessageFormat.format(linkPattern, href, page.name)); } } else if (element instanceof DocLink) { // ext link DocLink link = (DocLink) element; String href = getHref(link); sb.append(MessageFormat.format(linkPattern, href, link.name)); } else if (element instanceof DocDivider) { if (currentElement instanceof DocMenu) { // menu divider sb.append("<li class='divider'></li>\n"); } else { // nav bar divider sb.append("<li class='divider-vertical'></li>\n"); } } else if (element instanceof DocMenu) { // drop down menu DocMenu menu = (DocMenu) element; if (currentElement instanceof DocMenu) { // menu submenu sb.append("<li class='dropdown-submenu'> <!-- Submenu -->\n"); sb.append(MessageFormat .format("<a tabindex=''-1'' href=''#''>{0}</a>\n", menu.name)); sb.append("<ul class='dropdown-menu'>\n"); sb.append(createLinks(menu, menu.elements)); sb.append("</ul></li> <!-- End Submenu -->\n"); } else { // navbar menu sb.append("<li class='dropdown'> <!-- Menu -->\n"); sb.append(MessageFormat .format("<a class=''dropdown-toggle'' href=''#'' data-toggle=''dropdown''>{0}<b class=''caret''></b></a>\n", menu.name)); sb.append("<ul class='dropdown-menu'>\n"); sb.append(createLinks(menu, menu.elements)); sb.append("</ul></li> <!-- End Menu -->\n"); } } } sb.trimToSize(); return sb.toString(); } private static String getHref(DocElement element) { if (element instanceof DocLink) { // external link return ((DocLink) element).src; } else if (element instanceof DocPage) { // page link DocPage page = (DocPage) element; if (StringUtils.isEmpty(page.as)) { String html = page.src.substring(0, page.src.lastIndexOf('.')) + ".html"; return html; } return page.as; } return null; } private static String transformMarkdown(String comment) throws ParseException { String md = MarkdownUtils.transformMarkdown(comment); if (md.startsWith("<p>")) { md = md.substring(3); } if (md.endsWith("</p>")) { md = md.substring(0, md.length() - 4); } return md; } private static String generateHeader(String projectName, ToolkitConfig conf, Doc doc) { out.println("Generating HTML header..."); StringBuilder sb = new StringBuilder(); String header = readResource(doc, "header.html"); header = header.replace("%PROJECTNAME%", projectName); sb.append(header); if (doc.isResponsiveLayout) { sb.append("<!-- Responsive CSS must be included after the above body css! -->\n"); sb.append("<link rel='stylesheet' href='./bootstrap/css/bootstrap-responsive.min.css'>\n"); } if (doc.keywords != null && doc.keywords.size() > 0) { String keywords = StringUtils.flattenStrings(doc.keywords); sb.append(MessageFormat.format( "<meta name=\"keywords\" content=\"{0}\" />\n", keywords)); } if (doc.favicon != null) { sb.append("\n<link rel='shortcut icon' type='image/png' href='./" + doc.favicon.getName() + "' />"); } if (StringUtils.isEmpty(doc.prettifyTheme)) { // default theme sb.append("\n<link rel=\"stylesheet\" href=\"./prettify/prettify.css\" />"); } else { // custom theme sb.append(MessageFormat.format("\n<link rel=\"stylesheet\" href=\"./prettify/{0}\" />", doc.prettifyTheme + (doc.prettifyTheme.toLowerCase().endsWith(".css") ? "" : ".css"))); } if (!StringUtils.isEmpty(doc.rssFeed)) { sb.append(MessageFormat.format( "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{0} RSS 2.0\" href=\"./{1}\" />\n", doc.name, doc.rssFeed)); } if (!StringUtils.isEmpty(doc.atomFeed)) { sb.append(MessageFormat.format( "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"{0} Atom\" href=\"./{1}\" />\n", doc.name, doc.atomFeed)); } if (!StringUtils.isEmpty(doc.googlePlusId)) { String content = readResource(doc, "pluspage.html"); content = content.replace("%PLUSID%", doc.googlePlusId); sb.append('\n'); sb.append(content); sb.append('\n'); } if (doc.googlePlusOne && !StringUtils.isEmpty(conf.pom.url)) { String content = readResource(doc, "plusone.html"); content = content.replace("%URL%", conf.pom.url); sb.append('\n'); sb.append(content); sb.append('\n'); } if (doc.header != null && doc.header.exists()) { String content = FileUtils.readContent(doc.header, "\n"); if (!StringUtils.isEmpty(content)) { sb.append("\n<!-- Header Contribution -->\n"); sb.append(content); sb.append('\n'); } } return sb.toString(); } private static String generateFooter(Doc doc) { out.println("Generating HTML footer..."); final String date = new SimpleDateFormat("yyyy-MM-dd") .format(new Date()); String footer = readResource(doc, "footer.html"); if (doc.footer != null && doc.footer.exists()) { String custom = FileUtils.readContent(doc.footer, "\n"); if (!StringUtils.isEmpty(custom)) { footer = custom; } } footer = footer.replace("%PAGEDATE%", date); footer = footer .replace( "%PAGELICENSE%", "The content of this page is licensed under the <a href=\"http://creativecommons.org/licenses/by/3.0\">Creative Commons Attribution 3.0 License</a>."); return footer; } private static String generatePropertiesContent(Prop prop) throws Exception { BufferedReader propertiesReader = new BufferedReader(new FileReader( new File(prop.file))); Vector<Setting> settings = new Vector<Setting>(); List<String> comments = new ArrayList<String>(); String line = null; while ((line = propertiesReader.readLine()) != null) { if (line.length() == 0) { Setting s = new Setting("", "", comments); settings.add(s); comments.clear(); } else { if (line.charAt(0) == '#') { comments.add(line.substring(1).trim()); } else { String[] kvp = line.split("=", 2); String key = kvp[0].trim(); Setting s = new Setting(key, kvp[1].trim(), comments); settings.add(s); comments.clear(); } } } propertiesReader.close(); StringBuilder sb = new StringBuilder(); for (Setting setting : settings) { for (String comment : setting.comments) { if (prop.containsKeyword(comment)) { sb.append(MessageFormat .format("<span style=\"color:#004000;\"># <i>{0}</i></span>", transformMarkdown(comment))); } else { sb.append(MessageFormat.format( "<span style=\"color:#004000;\"># {0}</span>", transformMarkdown(comment))); } sb.append("<br/>\n"); } if (!StringUtils.isEmpty(setting.name)) { sb.append(MessageFormat .format("<span style=\"color:#000080;\">{0}</span> = <span style=\"color:#800000;\">{1}</span>", setting.name, StringUtils.escapeForHtml(setting.value, false))); } sb.append("<br/>\n"); } return sb.toString(); } protected static void processTemplates(Doc doc, DocPage page) throws TemplateException, MaxmlException, IOException { // templates if (page.templates != null && !page.templates.isEmpty()) { // Freemarker engine Configuration fm = new Configuration(); fm.setObjectWrapper(new DefaultObjectWrapper()); if (doc.templateDirectory != null && doc.templateDirectory.exists()) { fm.setDirectoryForTemplateLoading(doc.templateDirectory); } else { fm.setDirectoryForTemplateLoading(doc.sourceDirectory); } for (Template template : page.templates) { File data = new File(template.data); if (!data.exists()) { data = new File(doc.sourceDirectory, template.data); } MaxmlMap dataMap = Maxml.parse(data); // populate map with build properties by splitting them into maps for (Substitute sub : doc.substitutions) { if (sub.isProperty()) { String prop = sub.getPropertyName(); MaxmlMap keyMap = dataMap; // recursively create/find the destination map while (prop.indexOf('.') > -1) { String m = prop.substring(0, prop.indexOf('.')); if (!keyMap.containsKey(m)) { keyMap.put(m, new MaxmlMap()); } keyMap = keyMap.getMap(m); prop = prop.substring(m.length() + 1); } // inject property into map keyMap.put(prop, sub.value); } } // load and process the Freemarker template freemarker.template.Template ftl = fm.getTemplate(template.src); StringWriter writer = new StringWriter(); ftl.process(dataMap, writer); // create a substitution token Substitute sub = new Substitute(); sub.isTemplate = true; sub.token = template.token; sub.value = writer.toString(); doc.substitutions.add(sub); } } } /** * Setting represents a setting with its comments from the properties file. */ private static class Setting { final String name; final String value; final List<String> comments; Setting(String name, String value, List<String> comments) { this.name = name; this.value = value; this.comments = new ArrayList<String>(comments); } } private static class Section { String name; String id; Section(String id, String name) { this.id = id; this.name = name; } } }