com.vladsch.flexmark.html.renderer.CoreNodeRenderer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of flexmark Show documentation
Show all versions of flexmark Show documentation
Core of flexmark-java (implementation of CommonMark for parsing markdown and rendering to HTML)
package com.vladsch.flexmark.html.renderer;
import com.vladsch.flexmark.ast.*;
import com.vladsch.flexmark.ast.util.LineCollectingVisitor;
import com.vladsch.flexmark.ast.util.ReferenceRepository;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.HtmlRendererOptions;
import com.vladsch.flexmark.html.HtmlWriter;
import com.vladsch.flexmark.parser.ListOptions;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.NonRenderingInline;
import com.vladsch.flexmark.util.ast.TextCollectingVisitor;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.html.Attribute;
import com.vladsch.flexmark.util.html.Attributes;
import com.vladsch.flexmark.util.misc.CharPredicate;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import com.vladsch.flexmark.util.sequence.Escaping;
import com.vladsch.flexmark.util.sequence.Range;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.vladsch.flexmark.html.renderer.LinkStatus.UNKNOWN;
/**
* The node renderer that renders all the core nodes (comes last in the order of node renderers).
*/
@SuppressWarnings({ "WeakerAccess", "OverlyCoupledClass" })
public class CoreNodeRenderer implements NodeRenderer {
final public static AttributablePart LOOSE_LIST_ITEM = new AttributablePart("LOOSE_LIST_ITEM");
final public static AttributablePart TIGHT_LIST_ITEM = new AttributablePart("TIGHT_LIST_ITEM");
final public static AttributablePart PARAGRAPH_LINE = new AttributablePart("PARAGRAPH_LINE");
final public static AttributablePart CODE_CONTENT = new AttributablePart("FENCED_CODE_CONTENT");
final private ListOptions listOptions;
final private boolean obfuscateEmail;
final private boolean obfuscateEmailRandom;
final private ReferenceRepository referenceRepository;
final private boolean recheckUndefinedReferences;
final private boolean codeContentBlock;
final private boolean codeSoftLineBreaks;
private List myLines;
private List myEOLs;
private int myNextLine;
private int nextLineStartOffset;
public CoreNodeRenderer(DataHolder options) {
referenceRepository = Parser.REFERENCES.get(options);
recheckUndefinedReferences = HtmlRenderer.RECHECK_UNDEFINED_REFERENCES.get(options);
listOptions = ListOptions.get(options);
obfuscateEmail = HtmlRenderer.OBFUSCATE_EMAIL.get(options);
obfuscateEmailRandom = HtmlRenderer.OBFUSCATE_EMAIL_RANDOM.get(options);
codeContentBlock = Parser.FENCED_CODE_CONTENT_BLOCK.get(options);
codeSoftLineBreaks = Parser.CODE_SOFT_LINE_BREAKS.get(options);
myLines = null;
myEOLs = null;
myNextLine = 0;
nextLineStartOffset = 0;
}
@SuppressWarnings("OverlyCoupledMethod")
@Override
public Set> getNodeRenderingHandlers() {
return new HashSet<>(Arrays.asList(
new NodeRenderingHandler<>(AutoLink.class, this::render),
new NodeRenderingHandler<>(BlockQuote.class, this::render),
new NodeRenderingHandler<>(BulletList.class, this::render),
new NodeRenderingHandler<>(Code.class, this::render),
new NodeRenderingHandler<>(CodeBlock.class, this::render),
new NodeRenderingHandler<>(Document.class, this::render),
new NodeRenderingHandler<>(Emphasis.class, this::render),
new NodeRenderingHandler<>(FencedCodeBlock.class, this::render),
new NodeRenderingHandler<>(HardLineBreak.class, this::render),
new NodeRenderingHandler<>(Heading.class, this::render),
new NodeRenderingHandler<>(HtmlBlock.class, this::render),
new NodeRenderingHandler<>(HtmlCommentBlock.class, this::render),
new NodeRenderingHandler<>(HtmlInnerBlock.class, this::render),
new NodeRenderingHandler<>(HtmlInnerBlockComment.class, this::render),
new NodeRenderingHandler<>(HtmlEntity.class, this::render),
new NodeRenderingHandler<>(HtmlInline.class, this::render),
new NodeRenderingHandler<>(HtmlInlineComment.class, this::render),
new NodeRenderingHandler<>(Image.class, this::render),
new NodeRenderingHandler<>(ImageRef.class, this::render),
new NodeRenderingHandler<>(IndentedCodeBlock.class, this::render),
new NodeRenderingHandler<>(Link.class, this::render),
new NodeRenderingHandler<>(LinkRef.class, this::render),
new NodeRenderingHandler<>(BulletListItem.class, this::render),
new NodeRenderingHandler<>(OrderedListItem.class, this::render),
new NodeRenderingHandler<>(MailLink.class, this::render),
new NodeRenderingHandler<>(OrderedList.class, this::render),
new NodeRenderingHandler<>(Paragraph.class, this::render),
new NodeRenderingHandler<>(Reference.class, this::render),
new NodeRenderingHandler<>(SoftLineBreak.class, this::render),
new NodeRenderingHandler<>(StrongEmphasis.class, this::render),
new NodeRenderingHandler<>(Text.class, this::render),
new NodeRenderingHandler<>(TextBase.class, this::render),
new NodeRenderingHandler<>(ThematicBreak.class, this::render)
));
}
@SuppressWarnings("MethodMayBeStatic")
void render(Document node, NodeRendererContext context, HtmlWriter html) {
// No rendering itself
context.renderChildren(node);
}
@SuppressWarnings("MethodMayBeStatic")
void render(Heading node, NodeRendererContext context, HtmlWriter html) {
if (context.getHtmlOptions().renderHeaderId) {
String id = context.getNodeId(node);
if (id != null) {
html.attr("id", id);
}
}
if (context.getHtmlOptions().sourcePositionParagraphLines) {
html.srcPos(node.getChars()).withAttr().tagLine("h" + node.getLevel(), () -> {
html.srcPos(node.getText()).withAttr().tag("span");
context.renderChildren(node);
html.tag("/span");
});
} else {
html.srcPos(node.getText()).withAttr().tagLine("h" + node.getLevel(), () -> context.renderChildren(node));
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(BlockQuote node, NodeRendererContext context, HtmlWriter html) {
html.withAttr().tagLineIndent("blockquote", () -> context.renderChildren(node));
}
void render(FencedCodeBlock node, NodeRendererContext context, HtmlWriter html) {
html.line();
html.srcPosWithTrailingEOL(node.getChars()).withAttr().tag("pre").openPre();
BasedSequence info = node.getInfo();
HtmlRendererOptions htmlOptions = context.getHtmlOptions();
if (info.isNotNull() && !info.isBlank()) {
String language = node.getInfoDelimitedByAny(htmlOptions.languageDelimiterSet).unescape();
String languageClass = htmlOptions.languageClassMap.getOrDefault(language, htmlOptions.languageClassPrefix + language);
html.attr("class", languageClass);
} else {
String noLanguageClass = htmlOptions.noLanguageClass.trim();
if (!noLanguageClass.isEmpty()) {
html.attr("class", noLanguageClass);
}
}
html.srcPosWithEOL(node.getContentChars()).withAttr(CODE_CONTENT).tag("code");
if (codeContentBlock) {
context.renderChildren(node);
} else {
html.text(node.getContentChars().normalizeEOL());
}
html.tag("/code");
html.tag("/pre").closePre();
html.lineIf(htmlOptions.htmlBlockCloseTagEol);
}
@SuppressWarnings("MethodMayBeStatic")
void render(ThematicBreak node, NodeRendererContext context, HtmlWriter html) {
html.srcPos(node.getChars()).withAttr().tagVoidLine("hr");
}
void render(IndentedCodeBlock node, NodeRendererContext context, HtmlWriter html) {
html.line();
html.srcPosWithEOL(node.getChars()).withAttr().tag("pre").openPre();
String noLanguageClass = context.getHtmlOptions().noLanguageClass.trim();
if (!noLanguageClass.isEmpty()) {
html.attr("class", noLanguageClass);
}
html.srcPosWithEOL(node.getContentChars()).withAttr(CODE_CONTENT).tag("code");
if (codeContentBlock) {
context.renderChildren(node);
} else {
html.text(node.getContentChars().trimTailBlankLines().normalizeEndWithEOL());
}
html.tag("/code");
html.tag("/pre").closePre();
html.lineIf(context.getHtmlOptions().htmlBlockCloseTagEol);
}
@SuppressWarnings("MethodMayBeStatic")
void render(CodeBlock node, NodeRendererContext context, HtmlWriter html) {
if (node.getParent() instanceof IndentedCodeBlock) {
html.text(node.getContentChars().trimTailBlankLines().normalizeEndWithEOL());
} else {
html.text(node.getContentChars().normalizeEOL());
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(BulletList node, NodeRendererContext context, HtmlWriter html) {
html.withAttr().tagIndent("ul", () -> context.renderChildren(node));
}
void render(OrderedList node, NodeRendererContext context, HtmlWriter html) {
int start = node.getStartNumber();
if (listOptions.isOrderedListManualStart() && start != 1) html.attr("start", String.valueOf(start));
html.withAttr().tagIndent("ol", () -> context.renderChildren(node));
}
void render(BulletListItem node, NodeRendererContext context, HtmlWriter html) {
renderListItem(node, context, html);
}
void render(OrderedListItem node, NodeRendererContext context, HtmlWriter html) {
renderListItem(node, context, html);
}
private void renderListItem(ListItem node, NodeRendererContext context, HtmlWriter html) {
if (listOptions.isTightListItem(node)) {
html.srcPosWithEOL(node.getChars()).withAttr(TIGHT_LIST_ITEM).withCondIndent().tagLine("li", () -> {
html.text(node.getMarkerSuffix().unescape());
context.renderChildren(node);
});
} else {
html.srcPosWithEOL(node.getChars()).withAttr(LOOSE_LIST_ITEM).withCondIndent().tagLine("li", () -> {
html.text(node.getMarkerSuffix().unescape());
context.renderChildren(node);
});
}
}
public void renderTextBlockParagraphLines(Paragraph node, NodeRendererContext context, HtmlWriter html, boolean wrapTextInSpan) {
if (context.getHtmlOptions().sourcePositionParagraphLines) {
if (node.hasChildren()) {
LineCollectingVisitor breakCollectingVisitor = new LineCollectingVisitor();
myLines = breakCollectingVisitor.collectAndGetRanges(node);
myEOLs = breakCollectingVisitor.getEOLs();
myNextLine = 0;
if (node.getFirstChild() != null) {
outputSourceLineSpan(node, node.getFirstChild(), node, html);
}
context.renderChildren(node);
html.tag("/span");
return;
}
}
if (wrapTextInSpan) {
html.withAttr().tag("span", false, false, () -> context.renderChildren(node));
} else {
context.renderChildren(node);
}
}
private void outputSourceLineSpan(Node parentNode, Node startNode, Node endNode, HtmlWriter html) {
int startOffset = startNode.getStartOffset();
Range range = myLines.get(myNextLine);
int eolLength = myEOLs.get(myNextLine);
// remove trailing spaces from text
int endOffset = endNode.getEndOffset();
if (range.getEnd() <= endOffset) {
endOffset = range.getEnd();
endOffset -= eolLength;
endOffset -= parentNode.baseSubSequence(startOffset, endOffset).countTrailing(CharPredicate.SPACE_TAB);
myNextLine++;
nextLineStartOffset = range.getEnd();
nextLineStartOffset += parentNode.baseSubSequence(nextLineStartOffset, parentNode.getEndOffset()).countLeading(CharPredicate.SPACE_TAB);
}
if (range.getStart() > startOffset) {
startOffset = range.getStart();
}
html.srcPos(startOffset, endOffset).withAttr(PARAGRAPH_LINE).tag("span");
}
private void outputNextLineBreakSpan(Node node, HtmlWriter html, boolean outputBreakText) {
Range range = myLines.get(myNextLine);
int eolLength = myEOLs.get(myNextLine);
myNextLine++;
// remove trailing spaces from text
int countTrailing = node.baseSubSequence(nextLineStartOffset, range.getEnd() - eolLength).countTrailing(CharPredicate.SPACE_TAB);
if (!outputBreakText && countTrailing > 0) {
countTrailing--;
}
eolLength += countTrailing;
html.srcPos(nextLineStartOffset, range.getEnd() - eolLength).withAttr(PARAGRAPH_LINE).tag("span");
nextLineStartOffset = range.getEnd();
// remove leading spaces
nextLineStartOffset += node.baseSubSequence(nextLineStartOffset, node.getChars().getBaseSequence().length()).countLeading(CharPredicate.SPACE_TAB);
}
private void renderLooseParagraph(Paragraph node, NodeRendererContext context, HtmlWriter html) {
if (context.getHtmlOptions().noPTagsUseBr) {
renderTextBlockParagraphLines(node, context, html, false);
html.tagVoid("br").tagVoid("br").line();
} else {
html.srcPosWithEOL(node.getChars()).withAttr().tagLine("p", () -> renderTextBlockParagraphLines(node, context, html, false));
}
}
void render(Paragraph node, NodeRendererContext context, HtmlWriter html) {
if (node.getFirstChildAnyNot(NonRenderingInline.class) != null) {
if (!(node.getParent() instanceof ParagraphItemContainer)
|| !((ParagraphItemContainer) node.getParent()).isParagraphWrappingDisabled(node, listOptions, context.getOptions())) {
renderLooseParagraph(node, context, html);
} else {
renderTextBlockParagraphLines(node, context, html, false); //context.getHtmlOptions().wrapTightItemParagraphInSpan);
}
}
}
private boolean renderLineBreak(String breakText, String suppressInTag, Node node, NodeRendererContext context, HtmlWriter html) {
if (myLines != null && myNextLine < myLines.size()) {
// here we may need to close tags opened since the span tag
List openTags = html.getOpenTagsAfterLast("span");
int iMax = openTags.size();
boolean outputBreakText = iMax == 0 || suppressInTag == null || !suppressInTag.equalsIgnoreCase(openTags.get(iMax - 1));
if (!outputBreakText && !html.isPendingSpace()) {
// we add a space for EOL
html.raw(" ");
}
for (int i = iMax; i-- > 0; ) {
html.closeTag(openTags.get(i));
}
html.tag("/span");
if (outputBreakText) {
html.raw(breakText);
}
outputNextLineBreakSpan(node, html, outputBreakText);
for (String tag : openTags) {
if (!outputBreakText && context.getHtmlOptions().inlineCodeSpliceClass != null && !context.getHtmlOptions().inlineCodeSpliceClass.isEmpty()) {
html.attr(Attribute.CLASS_ATTR, context.getHtmlOptions().inlineCodeSpliceClass).withAttr().tag(tag);
} else {
html.tag(tag);
}
}
return true;
}
return false;
}
void render(SoftLineBreak node, NodeRendererContext context, HtmlWriter html) {
String softBreak = context.getHtmlOptions().softBreak;
if (context.getHtmlOptions().sourcePositionParagraphLines) {
if (renderLineBreak(softBreak, softBreak.equals("\n") || softBreak.equals("\r\n") || softBreak.equals("\r") ? "code" : null, node, context, html)) {
return;
}
}
html.raw(softBreak);
}
void render(HardLineBreak node, NodeRendererContext context, HtmlWriter html) {
if (context.getHtmlOptions().sourcePositionParagraphLines) {
if (renderLineBreak(context.getHtmlOptions().hardBreak, null, node, context, html)) {
return;
}
}
html.raw(context.getHtmlOptions().hardBreak);
}
@SuppressWarnings("MethodMayBeStatic")
void render(Emphasis node, NodeRendererContext context, HtmlWriter html) {
HtmlRendererOptions htmlOptions = context.getHtmlOptions();
if (htmlOptions.emphasisStyleHtmlOpen == null || htmlOptions.emphasisStyleHtmlClose == null) {
if (context.getHtmlOptions().sourcePositionParagraphLines) {
html.withAttr().tag("em");
} else {
html.srcPos(node.getText()).withAttr().tag("em");
}
context.renderChildren(node);
html.tag("/em");
} else {
html.raw(htmlOptions.emphasisStyleHtmlOpen);
context.renderChildren(node);
html.raw(htmlOptions.emphasisStyleHtmlClose);
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(StrongEmphasis node, NodeRendererContext context, HtmlWriter html) {
HtmlRendererOptions htmlOptions = context.getHtmlOptions();
if (htmlOptions.strongEmphasisStyleHtmlOpen == null || htmlOptions.strongEmphasisStyleHtmlClose == null) {
if (context.getHtmlOptions().sourcePositionParagraphLines) {
html.withAttr().tag("strong");
} else {
html.srcPos(node.getText()).withAttr().tag("strong");
}
context.renderChildren(node);
html.tag("/strong");
} else {
html.raw(htmlOptions.strongEmphasisStyleHtmlOpen);
context.renderChildren(node);
html.raw(htmlOptions.strongEmphasisStyleHtmlClose);
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(Text node, NodeRendererContext context, HtmlWriter html) {
html.text(Escaping.normalizeEOL(node.getChars().unescape()));
}
@SuppressWarnings("MethodMayBeStatic")
void render(TextBase node, NodeRendererContext context, HtmlWriter html) {
context.renderChildren(node);
}
void render(Code node, NodeRendererContext context, HtmlWriter html) {
HtmlRendererOptions htmlOptions = context.getHtmlOptions();
if (htmlOptions.codeStyleHtmlOpen == null || htmlOptions.codeStyleHtmlClose == null) {
if (context.getHtmlOptions().sourcePositionParagraphLines) {
html.withAttr().tag("code");
} else {
html.srcPos(node.getText()).withAttr().tag("code");
}
if (codeSoftLineBreaks && !htmlOptions.isSoftBreakAllSpaces) {
for (Node child : node.getChildren()) {
if (child instanceof Text) {
html.text(Escaping.collapseWhitespace(child.getChars(), true));
} else {
context.render(child);
}
}
} else {
html.text(Escaping.collapseWhitespace(node.getText(), true));
}
html.tag("/code");
} else {
html.raw(htmlOptions.codeStyleHtmlOpen);
if (codeSoftLineBreaks && !htmlOptions.isSoftBreakAllSpaces) {
for (Node child : node.getChildren()) {
if (child instanceof Text) {
html.text(Escaping.collapseWhitespace(child.getChars(), true));
} else {
context.render(child);
}
}
} else {
html.text(Escaping.collapseWhitespace(node.getText(), true));
}
html.raw(htmlOptions.codeStyleHtmlClose);
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlBlock node, NodeRendererContext context, HtmlWriter html) {
html.line();
HtmlRendererOptions htmlOptions = context.getHtmlOptions();
if (htmlOptions.sourceWrapHtmlBlocks) {
html.srcPos(node.getChars()).withAttr(AttributablePart.NODE_POSITION).tag("div").indent().line();
}
if (node.hasChildren()) {
// inner blocks handle rendering
context.renderChildren(node);
} else {
renderHtmlBlock(node, context, html, htmlOptions.suppressHtmlBlocks, htmlOptions.escapeHtmlBlocks, false);
}
if (htmlOptions.sourceWrapHtmlBlocks) {
html.unIndent().tag("/div");
}
html.lineIf(htmlOptions.htmlBlockCloseTagEol);
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlCommentBlock node, NodeRendererContext context, HtmlWriter html) {
renderHtmlBlock(node, context, html, context.getHtmlOptions().suppressHtmlCommentBlocks, context.getHtmlOptions().escapeHtmlCommentBlocks, false);
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlInnerBlock node, NodeRendererContext context, HtmlWriter html) {
renderHtmlBlock(node, context, html, context.getHtmlOptions().suppressHtmlBlocks, context.getHtmlOptions().escapeHtmlBlocks, false);
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlInnerBlockComment node, NodeRendererContext context, HtmlWriter html) {
renderHtmlBlock(node, context, html, context.getHtmlOptions().suppressHtmlCommentBlocks, context.getHtmlOptions().escapeHtmlCommentBlocks, false);
}
public static void renderHtmlBlock(HtmlBlockBase node, NodeRendererContext context, HtmlWriter html, boolean suppress, boolean escape, boolean trimSpaces) {
if (suppress) return;
if (node instanceof HtmlBlock)
html.line();
String normalizeEOL = node instanceof HtmlBlock ? node.getContentChars().normalizeEOL() : node.getChars().normalizeEOL();
if (trimSpaces) {
normalizeEOL = normalizeEOL.trim();
}
if (escape) {
if (node instanceof HtmlBlock) {
if (normalizeEOL.length() > 0 && normalizeEOL.charAt(normalizeEOL.length() - 1) == '\n') {
// leave off the trailing EOL
normalizeEOL = normalizeEOL.substring(0, normalizeEOL.length() - 1);
}
html.raw("").text(normalizeEOL).raw("
");
} else {
html.text(normalizeEOL);
}
} else {
html.rawPre(normalizeEOL);
}
if (node instanceof HtmlBlock) {
html.lineIf(context.getHtmlOptions().htmlBlockCloseTagEol);
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlInline node, NodeRendererContext context, HtmlWriter html) {
//if (context.getHtmlOptions().sourceWrapInlineHtml) {
// html.srcPos(node.getChars()).withAttr(AttributablePart.NODE_POSITION).tag("span");
//}
renderInlineHtml(node, context, html, context.getHtmlOptions().suppressInlineHtml, context.getHtmlOptions().escapeInlineHtml);
//if (context.getHtmlOptions().sourceWrapInlineHtml) {
// html.tag("/span");
//}
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlInlineComment node, NodeRendererContext context, HtmlWriter html) {
renderInlineHtml(node, context, html, context.getHtmlOptions().suppressInlineHtmlComments, context.getHtmlOptions().escapeInlineHtmlComments);
}
public static void renderInlineHtml(HtmlInlineBase node, NodeRendererContext context, HtmlWriter html, boolean suppress, boolean escape) {
if (suppress) return;
if (escape) {
html.text(node.getChars().normalizeEOL());
} else {
html.rawPre(node.getChars().normalizeEOL());
}
}
void render(Reference node, NodeRendererContext context, HtmlWriter html) {
}
@SuppressWarnings("MethodMayBeStatic")
void render(HtmlEntity node, NodeRendererContext context, HtmlWriter html) {
if (context.getHtmlOptions().unescapeHtmlEntities) {
html.text(node.getChars().unescape());
} else {
html.raw(node.getChars().unescapeNoEntities());
}
}
public static boolean isSuppressedLinkPrefix(CharSequence url, NodeRendererContext context) {
Pattern suppressedLinks = context.getHtmlOptions().suppressedLinks;
if (suppressedLinks != null) {
Matcher matcher = suppressedLinks.matcher(url);
return matcher.matches();
}
return false;
}
@SuppressWarnings("MethodMayBeStatic")
void render(AutoLink node, NodeRendererContext context, HtmlWriter html) {
String text = node.getText().toString();
if (context.isDoNotRenderLinks() || isSuppressedLinkPrefix(node.getUrl(), context)) {
html.text(text);
} else {
ResolvedLink resolvedLink = context.resolveLink(LinkType.LINK, text, null);
html.srcPos(node.getText()).attr("href", resolvedLink.getUrl().startsWith("www.") ? context.getHtmlOptions().autolinkWwwPrefix + resolvedLink.getUrl() : resolvedLink.getUrl())
.withAttr(resolvedLink)
.tag("a", false, false, () -> html.text(text));
}
}
void render(MailLink node, NodeRendererContext context, HtmlWriter html) {
String text = node.getText().unescape();
if (context.isDoNotRenderLinks() || isSuppressedLinkPrefix(node.getUrl(), context)) {
html.text(text);
} else {
ResolvedLink resolvedLink = context.resolveLink(LinkType.LINK, text, null);
if (obfuscateEmail) {
String url = Escaping.obfuscate("mailto:" + resolvedLink.getUrl(), obfuscateEmailRandom);
text = Escaping.obfuscate(text, true);
html.srcPos(node.getText()).attr("href", url)
.withAttr(resolvedLink)
.tag("a")
.raw(text)
.tag("/a");
} else {
String url = resolvedLink.getUrl();
html.srcPos(node.getText()).attr("href", "mailto:" + url)
.withAttr(resolvedLink)
.tag("a")
.text(text)
.tag("/a");
}
}
}
@SuppressWarnings("MethodMayBeStatic")
void render(Image node, NodeRendererContext context, HtmlWriter html) {
if (!(context.isDoNotRenderLinks() || isSuppressedLinkPrefix(node.getUrl(), context))) {
String altText = new TextCollectingVisitor().collectAndGetText(node);
ResolvedLink resolvedLink = context.resolveLink(LinkType.IMAGE, node.getUrl().unescape(), null, null);
String url = resolvedLink.getUrl();
if (!node.getUrlContent().isEmpty()) {
// reverse URL encoding of =, &
String content = Escaping.percentEncodeUrl(node.getUrlContent()).replace("+", "%2B").replace("%3D", "=").replace("%26", "&");
url += content;
}
html.attr("src", url);
html.attr("alt", altText);
// we have a title part, use that
if (node.getTitle().isNotNull()) {
resolvedLink.getNonNullAttributes().replaceValue(Attribute.TITLE_ATTR, node.getTitle().unescape());
} else {
resolvedLink.getNonNullAttributes().remove(Attribute.TITLE_ATTR);
}
html.attr(resolvedLink.getAttributes());
html.srcPos(node.getChars()).withAttr(resolvedLink).tagVoid("img");
}
}
void render(Link node, NodeRendererContext context, HtmlWriter html) {
if (context.isDoNotRenderLinks() || isSuppressedLinkPrefix(node.getUrl(), context)) {
context.renderChildren(node);
} else {
ResolvedLink resolvedLink = context.resolveLink(LinkType.LINK, node.getUrl().unescape(), null, null);
html.attr("href", resolvedLink.getUrl());
// we have a title part, use that
if (node.getTitle().isNotNull()) {
resolvedLink.getNonNullAttributes().replaceValue(Attribute.TITLE_ATTR, node.getTitle().unescape());
} else {
resolvedLink.getNonNullAttributes().remove(Attribute.TITLE_ATTR);
}
html.attr(resolvedLink.getAttributes());
html.srcPos(node.getChars()).withAttr(resolvedLink).tag("a");
renderChildrenSourceLineWrapped(node, node.getText(), context, html);
html.tag("/a");
}
}
private void renderChildrenSourceLineWrapped(
Node node,
BasedSequence nodeChildText,
NodeRendererContext context,
HtmlWriter html
) {
// if have SOFT BREAK or HARD BREAK as child then we open our own span
if (context.getHtmlOptions().sourcePositionParagraphLines && nodeChildText.indexOfAny(CharPredicate.ANY_EOL) >= 0) {
if (myNextLine > 0) {
myNextLine--;
}
outputSourceLineSpan(node, node, node, html);
context.renderChildren(node);
html.tag("/span");
} else {
context.renderChildren(node);
}
}
void render(ImageRef node, NodeRendererContext context, HtmlWriter html) {
ResolvedLink resolvedLink;
boolean isSuppressed = false;
if (!node.isDefined() && recheckUndefinedReferences) {
if (node.getReferenceNode(referenceRepository) != null) {
node.setDefined(true);
}
}
Reference reference = null;
if (node.isDefined()) {
reference = node.getReferenceNode(referenceRepository);
String url = reference.getUrl().unescape();
isSuppressed = isSuppressedLinkPrefix(url, context);
resolvedLink = context.resolveLink(LinkType.IMAGE, url, null, null);
if (reference.getTitle().isNotNull()) {
resolvedLink.getNonNullAttributes().replaceValue(Attribute.TITLE_ATTR, reference.getTitle().unescape());
} else {
resolvedLink.getNonNullAttributes().remove(Attribute.TITLE_ATTR);
}
} else {
// see if have reference resolver and this is resolved
String normalizeRef = referenceRepository.normalizeKey(node.getReference());
resolvedLink = context.resolveLink(LinkType.IMAGE_REF, normalizeRef, null, null);
if (resolvedLink.getStatus() == UNKNOWN || resolvedLink.getUrl().isEmpty()) {
resolvedLink = null;
}
}
if (resolvedLink == null) {
// empty ref, we treat it as text
html.text(node.getChars().unescape());
} else {
if (!(context.isDoNotRenderLinks() || isSuppressed)) {
String altText = new TextCollectingVisitor().collectAndGetText(node);
Attributes attributes = resolvedLink.getNonNullAttributes();
html.attr("src", resolvedLink.getUrl());
html.attr("alt", altText);
// need to take attributes for reference definition, then overlay them with ours
if (reference != null) {
attributes = context.extendRenderingNodeAttributes(reference, AttributablePart.NODE, attributes);
}
html.attr(attributes);
html.srcPos(node.getChars()).withAttr(resolvedLink).tagVoid("img");
}
}
}
void render(LinkRef node, NodeRendererContext context, HtmlWriter html) {
ResolvedLink resolvedLink;
boolean isSuppressed = false;
if (!node.isDefined() && recheckUndefinedReferences) {
if (node.getReferenceNode(referenceRepository) != null) {
node.setDefined(true);
}
}
Reference reference = null;
if (node.isDefined()) {
reference = node.getReferenceNode(referenceRepository);
String url = reference.getUrl().unescape();
isSuppressed = isSuppressedLinkPrefix(url, context);
resolvedLink = context.resolveLink(LinkType.LINK, url, null, null);
if (reference.getTitle().isNotNull()) {
resolvedLink.getNonNullAttributes().replaceValue(Attribute.TITLE_ATTR, reference.getTitle().unescape());
} else {
resolvedLink.getNonNullAttributes().remove(Attribute.TITLE_ATTR);
}
} else {
// see if have reference resolver and this is resolved
String normalizeRef = node.getReference().unescape();
resolvedLink = context.resolveLink(LinkType.LINK_REF, normalizeRef, null, null);
if (resolvedLink.getStatus() == UNKNOWN || resolvedLink.getUrl().isEmpty()) {
resolvedLink = null;
}
}
if (resolvedLink == null) {
// empty ref, we treat it as text
assert !node.isDefined();
if (!node.hasChildren()) {
html.text(node.getChars().unescape());
} else {
html.text(node.getChars().prefixOf(node.getChildChars()).unescape());
renderChildrenSourceLineWrapped(node, node.getText(), context, html);
html.text(node.getChars().suffixOf(node.getChildChars()).unescape());
}
} else {
if (context.isDoNotRenderLinks() || isSuppressed) {
context.renderChildren(node);
} else {
Attributes attributes = resolvedLink.getNonNullAttributes();
html.attr("href", resolvedLink.getUrl());
if (reference != null) {
attributes = context.extendRenderingNodeAttributes(reference, AttributablePart.NODE, attributes);
}
html.attr(attributes);
html.srcPos(node.getChars()).withAttr(resolvedLink).tag("a");
renderChildrenSourceLineWrapped(node, node.getText(), context, html);
html.tag("/a");
}
}
}
public static class Factory implements NodeRendererFactory {
@NotNull
@Override
public NodeRenderer apply(@NotNull DataHolder options) {
return new CoreNodeRenderer(options);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy