flexmark-java
是java
版的Markdown
转换工具,基本支持Markdown
所有的语法,而且扩展性也不错;本文主要是通过扩展形式给链接添加target
属性
本文的扩展还将支持
Spring Properties
来动态配置, 支持域名排除、支持相对路径排除、支持自定义target
属性的值.
定义Properties配置类 LinkTargetProperties
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @author :yepk
* @version :1.0
* @apiNote :链接转换加target
* @date :2020-08-11-9:36
*/
@Configuration
@ConfigurationProperties(prefix = "markdown.link")
@Data
public class LinkTargetProperties {
/**
* 排除添加 target 属性的链接
*/
private List<String> excludes;
/**
* target 属性的值
*/
private String target = "_target";
/**
* 相对路径排除
*/
private boolean relativeExclude = true;
}
实现 AttributeProvider的类 LinkTargetAttributeProvider
扩展
flexmark-java
主要是通过实现AttributeProvider
进行修改
其中
@NotNull
必须用org.jetbrains.annotations包下的。
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.html.AttributeProvider;
import com.vladsch.flexmark.html.AttributeProviderFactory;
import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
import com.vladsch.flexmark.html.renderer.AttributablePart;
import com.vladsch.flexmark.html.renderer.LinkResolverContext;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.html.Attribute;
import com.vladsch.flexmark.util.html.Attributes;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.compile;
/**
* @author :yepk
* @version :1.0
* @apiNote :链接加target
* @date :2020-08-11-9:37
*/
public class LinkTargetAttributeProvider implements AttributeProvider {
// 用于获取配置的数据
private final DataHolder dataHolder;
// 绝对路径正则匹配
private final Pattern pattern = compile("^[a-zA-z]+://[^\\s]*");
public LinkTargetAttributeProvider(DataHolder dataHolder) {
this.dataHolder = dataHolder;
}
@Override
public void setAttributes(@NotNull Node node, @NotNull AttributablePart part, @NotNull Attributes attributes) {
// 只处理 Link
if (node instanceof Link && part == AttributablePart.LINK) {
// 获取 href 标签
Attribute hrefAttr = attributes.get("href");
if (hrefAttr == null) {
return;
}
// 值也不能为空
String href = hrefAttr.getValue();
if (StringUtils.isEmpty(href)) {
return;
}
// 获取配置参数
// 注意此处不能直接使用 Spring Boot 的依赖注入
// 但可以使用ApplicatonContext.getBean的形式获取
LinkTargetProperties dataKey = LinkTargetExtensions.LINK_TARGET.get(this.dataHolder);
// 判断是否是绝对路径
if (!pattern.matcher(href).matches()) {
if (dataKey.isRelativeExclude()) {
// 如果是相对路径,则排除
return;
}
} else {
// 获取域名/host
String host = getHost(href);
if (host == null) {
return;
}
List<String> excludes = dataKey.getExcludes();
if (excludes != null && !excludes.isEmpty()) {
// 如果包含当前的host则排除
if (excludes.contains(host)) {
return;
}
}
}
String target = dataKey.getTarget();
if (StringUtils.isEmpty(target)) {
target = "";
}
// 设置target 属性
attributes.replaceValue("target", target);
}
}
static AttributeProviderFactory factory() {
return new IndependentAttributeProviderFactory() {
@Override
public @NotNull AttributeProvider apply(@NotNull LinkResolverContext linkResolverContext) {
// 在此处获取dataHolder
return new LinkTargetAttributeProvider(linkResolverContext.getOptions());
}
};
}
public static String getHost(String url) {
if (url == null || "".equals(url.trim())) {
return null;
}
String host = "";
Pattern p = compile("(?<=//|)((\\w)+\\.)+\\w+");
Matcher matcher = p.matcher(url);
if (matcher.find()) {
host = matcher.group();
}
return host;
}
}
注册 Provider
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.util.data.DataKey;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import org.jetbrains.annotations.NotNull;
/**
* @author :yepk
* @version :1.0
* @apiNote :链接
* @date :2020-08-11-9:46
*/
public class LinkTargetExtensions implements HtmlRenderer.HtmlRendererExtension {
// 定义配置参数
// 并设置默认值
public static final DataKey<LinkTargetProperties> LINK_TARGET = new DataKey<>("LINK_TARGET", new LinkTargetProperties());
@Override
public void rendererOptions(@NotNull MutableDataHolder mutableDataHolder) {
}
@Override
public void extend(HtmlRenderer.@NotNull Builder builder, @NotNull String s) {
builder.attributeProviderFactory(LinkTargetAttributeProvider.factory());
}
public static LinkTargetExtensions create() {
return new LinkTargetExtensions();
}
}
Markdown 工具类
public class MarkdownUtil{
private static final MutableDataSet OPTIONS = new MutableDataSet(
PegdownOptionsAdapter.flexmarkOptions(
true,
// 所有的特性
Extensions.ALL,
// 自定义的 Link Target 扩展
LinkTargetExtensions.create()
))
.set(HtmlRenderer.SOFT_BREAK, "<br/>");
// 解析器
private static final Parser PARSER = Parser.builder(OPTIONS).build();
// 渲染器
private static final HtmlRenderer htmlRender = HtmlRenderer.builder(OPTIONS).build();
/**
* 渲染 Markdown
* @param markdown 文档
* @param JDK8的Consumer的,用于动态改变 LinkTargetAttributeProvider的配置参数
* @return html
*/
public static String renderHtml(String markdown, Consumer<Document> accept) {
if (Util.isEmpty(markdown)) {
return "";
}
Document document = PARSER.parse(markdown);
if (accept != null) {
accept.accept(document);
}
return htmlRender.render(document);
}
}
使用示例
public class TestMarkdownRender(){
public static void main(String[] args) {
String markdown = "[测试1](http://www.yepk.cn/test1 '测试1') [测试2](/test2 '测试2') [测试3](https://www.yepk.cn/test3 '测试3')";
//1. 默认的配置属性 一般直接用即可
System.out.println(renderHtml(markdown));
//2. 自定义属性值
System.out.println(MarkdownUtil.renderHtml(markdown, doc -> {
doc.set(LinkTargetExtensions.LINK_TARGET, new LinkTargetProperties(
// 需要过滤的域名
Arrays.asList("www.itlangzi.com","www.baidu.com"),
// target 属性的值
"_target",
// 排除相对路径
true
));
}));
}
}
结果
下面原文地址也是效果
原文链接:IT浪子の博客