flexmark-java markdown给链接添加target属性(修改版,可直接用)

flexmark-javajava 版的 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浪子の博客

评论

企鹅群:39438021

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×