org.pegdown.ToHtmlSerializer Maven / Gradle / Ivy
/*
* Copyright (C) 2010-2011 Mathias Doenitz
*
* Based on peg-markdown (C) 2008-2010 John MacFarlane
*
* 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.pegdown;
import org.parboiled.common.StringUtils;
import org.pegdown.ast.*;
import org.pegdown.plugins.ToHtmlSerializerPlugin;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.parboiled.common.Preconditions.checkArgNotNull;
public class ToHtmlSerializer implements Visitor {
protected Printer printer = new Printer();
protected final Map references = new HashMap();
protected final Map abbreviations = new HashMap();
protected final LinkRenderer linkRenderer;
protected final List plugins;
protected TableNode currentTableNode;
protected int currentTableColumn;
protected boolean inTableHeader;
protected Map verbatimSerializers;
public ToHtmlSerializer(LinkRenderer linkRenderer) {
this(linkRenderer, Collections.emptyList());
}
public ToHtmlSerializer(LinkRenderer linkRenderer, List plugins) {
this(linkRenderer, Collections.emptyMap(), plugins);
}
public ToHtmlSerializer(final LinkRenderer linkRenderer, final Map verbatimSerializers) {
this(linkRenderer, verbatimSerializers, Collections.emptyList());
}
public ToHtmlSerializer(final LinkRenderer linkRenderer, final Map verbatimSerializers, final List plugins) {
this.linkRenderer = linkRenderer;
this.verbatimSerializers = new HashMap(verbatimSerializers);
if (!this.verbatimSerializers.containsKey(VerbatimSerializer.DEFAULT)) {
this.verbatimSerializers.put(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE);
}
this.plugins = plugins;
}
public String toHtml(RootNode astRoot) {
checkArgNotNull(astRoot, "astRoot");
astRoot.accept(this);
return printer.getString();
}
public void visit(RootNode node) {
for (ReferenceNode refNode : node.getReferences()) {
visitChildren(refNode);
references.put(normalize(printer.getString()), refNode);
printer.clear();
}
for (AbbreviationNode abbrNode : node.getAbbreviations()) {
visitChildren(abbrNode);
String abbr = printer.getString();
printer.clear();
abbrNode.getExpansion().accept(this);
String expansion = printer.getString();
abbreviations.put(abbr, expansion);
printer.clear();
}
visitChildren(node);
}
public void visit(AbbreviationNode node) {
}
public void visit(AnchorLinkNode node) {
printLink(linkRenderer.render(node));
}
public void visit(AutoLinkNode node) {
printLink(linkRenderer.render(node));
}
public void visit(BlockQuoteNode node) {
printIndentedTag(node, "blockquote");
}
public void visit(BulletListNode node) {
printIndentedTag(node, "ul");
}
public void visit(CodeNode node) {
printTag(node, "code");
}
public void visit(DefinitionListNode node) {
printIndentedTag(node, "dl");
}
public void visit(DefinitionNode node) {
printConditionallyIndentedTag(node, "dd");
}
public void visit(DefinitionTermNode node) {
printConditionallyIndentedTag(node, "dt");
}
public void visit(ExpImageNode node) {
String text = printChildrenToString(node);
printImageTag(linkRenderer.render(node, text));
}
public void visit(ExpLinkNode node) {
String text = printChildrenToString(node);
printLink(linkRenderer.render(node, text));
}
public void visit(HeaderNode node) {
printBreakBeforeTag(node, "h" + node.getLevel());
}
public void visit(HtmlBlockNode node) {
String text = node.getText();
if (text.length() > 0) printer.println();
printer.print(text);
}
public void visit(InlineHtmlNode node) {
printer.print(node.getText());
}
public void visit(ListItemNode node) {
if (node instanceof TaskListNode) {
// vsch: #185 handle GitHub style task list items, these are a bit messy because the checkbox needs to be
// included inside the optional first grand-child of the list item, first child is always RootNode
// because the list item text is recursively parsed.
Node firstChild = node.getChildren().get(0).getChildren().get(0);
boolean firstIsPara = firstChild instanceof ParaNode;
int indent = node.getChildren().size() > 1 ? 2 : 0;
boolean startWasNewLine = printer.endsWithNewLine();
printer.println().print("").indent(indent);
if (firstIsPara) {
printer.println().print("");
printer.print("");
visitChildren((SuperNode) firstChild);
// render the other children, the p tag is taken care of here
visitChildrenSkipFirst(node);
printer.print("
");
} else {
printer.print("");
visitChildren(node);
}
printer.indent(-indent).printchkln(indent != 0).print(" ")
.printchkln(startWasNewLine);
} else {
printConditionallyIndentedTag(node, "li");
}
}
public void visit(MailLinkNode node) {
printLink(linkRenderer.render(node));
}
public void visit(OrderedListNode node) {
printIndentedTag(node, "ol");
}
public void visit(ParaNode node) {
printBreakBeforeTag(node, "p");
}
public void visit(QuotedNode node) {
switch (node.getType()) {
case DoubleAngle:
printer.print("«");
visitChildren(node);
printer.print("»");
break;
case Double:
printer.print("“");
visitChildren(node);
printer.print("”");
break;
case Single:
printer.print("‘");
visitChildren(node);
printer.print("’");
break;
}
}
public void visit(ReferenceNode node) {
// reference nodes are not printed
}
public void visit(RefImageNode node) {
String text = printChildrenToString(node);
String key = node.referenceKey != null ? printChildrenToString(node.referenceKey) : text;
ReferenceNode refNode = references.get(normalize(key));
if (refNode == null) { // "fake" reference image link
printer.print("![").print(text).print(']');
if (node.separatorSpace != null) {
printer.print(node.separatorSpace).print('[');
if (node.referenceKey != null) printer.print(key);
printer.print(']');
}
} else printImageTag(linkRenderer.render(node, refNode.getUrl(), refNode.getTitle(), text));
}
public void visit(RefLinkNode node) {
String text = printChildrenToString(node);
String key = node.referenceKey != null ? printChildrenToString(node.referenceKey) : text;
ReferenceNode refNode = references.get(normalize(key));
if (refNode == null) { // "fake" reference link
printer.print('[').print(text).print(']');
if (node.separatorSpace != null) {
printer.print(node.separatorSpace).print('[');
if (node.referenceKey != null) printer.print(key);
printer.print(']');
}
} else printLink(linkRenderer.render(node, refNode.getUrl(), refNode.getTitle(), text));
}
public void visit(SimpleNode node) {
switch (node.getType()) {
case Apostrophe:
printer.print("’");
break;
case Ellipsis:
printer.print("…");
break;
case Emdash:
printer.print("—");
break;
case Endash:
printer.print("–");
break;
case HRule:
printer.println().print("
");
break;
case Linebreak:
printer.print("
");
break;
case Nbsp:
printer.print(" ");
break;
default:
throw new IllegalStateException();
}
}
public void visit(StrongEmphSuperNode node) {
if (node.isClosed()) {
if (node.isStrong())
printTag(node, "strong");
else
printTag(node, "em");
} else {
//sequence was not closed, treat open chars as ordinary chars
printer.print(node.getChars());
visitChildren(node);
}
}
public void visit(StrikeNode node) {
printTag(node, "del");
}
public void visit(TableBodyNode node) {
printIndentedTag(node, "tbody");
}
@Override
public void visit(TableCaptionNode node) {
printer.println().print("");
visitChildren(node);
printer.print(" ");
}
public void visit(TableCellNode node) {
String tag = inTableHeader ? "th" : "td";
List columns = currentTableNode.getColumns();
TableColumnNode column = columns.get(Math.min(currentTableColumn, columns.size() - 1));
printer.println().print('<').print(tag);
column.accept(this);
if (node.getColSpan() > 1) printer.print(" colspan=\"").print(Integer.toString(node.getColSpan())).print('"');
printer.print('>');
visitChildren(node);
printer.print('<').print('/').print(tag).print('>');
currentTableColumn += node.getColSpan();
}
public void visit(TableColumnNode node) {
switch (node.getAlignment()) {
case None:
break;
case Left:
printer.print(" align=\"left\"");
break;
case Right:
printer.print(" align=\"right\"");
break;
case Center:
printer.print(" align=\"center\"");
break;
default:
throw new IllegalStateException();
}
}
public void visit(TableHeaderNode node) {
inTableHeader = true;
printIndentedTag(node, "thead");
inTableHeader = false;
}
public void visit(TableNode node) {
currentTableNode = node;
printIndentedTag(node, "table");
currentTableNode = null;
}
public void visit(TableRowNode node) {
currentTableColumn = 0;
printIndentedTag(node, "tr");
}
public void visit(VerbatimNode node) {
VerbatimSerializer serializer = lookupSerializer(node.getType());
serializer.serialize(node, printer);
}
protected VerbatimSerializer lookupSerializer(final String type) {
if (type != null && verbatimSerializers.containsKey(type)) {
return verbatimSerializers.get(type);
} else {
return verbatimSerializers.get(VerbatimSerializer.DEFAULT);
}
}
public void visit(WikiLinkNode node) {
printLink(linkRenderer.render(node));
}
public void visit(TextNode node) {
if (abbreviations.isEmpty()) {
printer.print(node.getText());
} else {
printWithAbbreviations(node.getText());
}
}
public void visit(SpecialTextNode node) {
printer.printEncoded(node.getText());
}
public void visit(SuperNode node) {
visitChildren(node);
}
public void visit(Node node) {
for (ToHtmlSerializerPlugin plugin : plugins) {
if (plugin.visit(node, this, printer)) {
return;
}
}
// override this method for processing custom Node implementations
throw new RuntimeException("Don't know how to handle node " + node);
}
// helpers
protected void visitChildren(SuperNode node) {
for (Node child : node.getChildren()) {
child.accept(this);
}
}
// helpers
protected void visitChildrenSkipFirst(SuperNode node) {
boolean first = true;
for (Node child : node.getChildren()) {
if (!first) child.accept(this);
first = false;
}
}
protected void printTag(TextNode node, String tag) {
printer.print('<').print(tag).print('>');
printer.printEncoded(node.getText());
printer.print('<').print('/').print(tag).print('>');
}
protected void printTag(SuperNode node, String tag) {
printer.print('<').print(tag).print('>');
visitChildren(node);
printer.print('<').print('/').print(tag).print('>');
}
protected void printBreakBeforeTag(SuperNode node, String tag) {
boolean startWasNewLine = printer.endsWithNewLine();
printer.println();
printTag(node, tag);
if (startWasNewLine) printer.println();
}
protected void printIndentedTag(SuperNode node, String tag) {
printer.println().print('<').print(tag).print('>').indent(+2);
visitChildren(node);
printer.indent(-2).println().print('<').print('/').print(tag).print('>');
}
protected void printConditionallyIndentedTag(SuperNode node, String tag) {
if (node.getChildren().size() > 1) {
printer.println().print('<').print(tag).print('>').indent(+2);
visitChildren(node);
printer.indent(-2).println().print('<').print('/').print(tag).print('>');
} else {
boolean startWasNewLine = printer.endsWithNewLine();
printer.println().print('<').print(tag).print('>');
visitChildren(node);
printer.print('<').print('/').print(tag).print('>').printchkln(startWasNewLine);
}
}
protected void printImageTag(LinkRenderer.Rendering rendering) {
printer.print("");
}
protected void printLink(LinkRenderer.Rendering rendering) {
printer.print('<').print('a');
printAttribute("href", rendering.href);
for (LinkRenderer.Attribute attr : rendering.attributes) {
printAttribute(attr.name, attr.value);
}
printer.print('>').print(rendering.text).print("");
}
protected void printAttribute(String name, String value) {
printer.print(' ').print(name).print('=').print('"').print(value).print('"');
}
protected String printChildrenToString(SuperNode node) {
Printer priorPrinter = printer;
printer = new Printer();
visitChildren(node);
String result = printer.getString();
printer = priorPrinter;
return result;
}
protected String normalize(String string) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
switch (c) {
case ' ':
case '\n':
case '\t':
continue;
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
protected void printWithAbbreviations(String string) {
Map> expansions = null;
for (Map.Entry entry : abbreviations.entrySet()) {
// first check, whether we have a legal match
String abbr = entry.getKey();
int ix = 0;
while (true) {
int sx = string.indexOf(abbr, ix);
if (sx == -1) break;
// only allow whole word matches
ix = sx + abbr.length();
if (sx > 0 && Character.isLetterOrDigit(string.charAt(sx - 1))) continue;
if (ix < string.length() && Character.isLetterOrDigit(string.charAt(ix))) {
continue;
}
// ok, legal match so save an expansions "task" for all matches
if (expansions == null) {
expansions = new TreeMap>();
}
expansions.put(sx, entry);
}
}
if (expansions != null) {
int ix = 0;
for (Map.Entry> entry : expansions.entrySet()) {
int sx = entry.getKey();
String abbr = entry.getValue().getKey();
String expansion = entry.getValue().getValue();
printer.printEncoded(string.substring(ix, sx));
printer.print("');
printer.printEncoded(abbr);
printer.print("");
ix = sx + abbr.length();
}
printer.print(string.substring(ix));
} else {
printer.print(string);
}
}
}