flexmark-java markdown給鏈接添加target屬性

  • flexmark-javajava 版的 Markdown 轉換工具,基本支持 Markdown 所有的語法,而且擴展性也不錯;本文主要是通過擴展形式給鏈接添加 target 屬性
  • 本文的擴展還將支持 Spring Properties 來動態配置, 支持域名排除、支持相對路徑排除、支持自定義 target 屬性的值.

定義Properties配置類 LinkTargetProperties

@Configuration
@ConfigurationProperties(prefix = "markdown.link")
public class LinkTargetProperties{
    /**
     * 排除添加 target 屬性的鏈接
     */
    private List<String> excludes;
    /**
     * target 屬性的值
     */
    private String target = "_target";

    /**
     * 相對路徑排除
     */
    private boolean relativeExclude = true;
    
    // get 和 set 方法省略
}

實現 AttributeProvider的類 LinkTargetAttributeProvider

擴展 flexmark-java 主要是通過實現 AttributeProvider 進行修改

public class LinkTargetAttributeProvider implements AttributeProvider {
    // 用於獲取配置的數據
    private final DataHolder dataHolder;
    // 絕對路徑正則匹配
    private final Pattern 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 = FlexmarkExtensions.LINK_TARGET.get(this.dataHolder);
            
            // 判斷是否是絕對路徑
            if (!pattern.matcher(href).matches()) {
                if (dataKey.isRelativeExclude()) {
                    // 如果是相對路徑,則排除
                    return;
                }
            } else {
                
                // 獲取域名/host
                Optional<String> host = ServletUtil.getHost(href);
                if (host.isEmpty()) {
                    return;
                }
                List<String> excludes = dataKey.getExcludes();
                if (excludes != null && !excludes.isEmpty()) {
                    
                    // 如果包含當前的host則排除
                    if (excludes.contains(host.get())) {
                        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());
            }
        };
    }
}

在就版本中也可以通過 DataHoler.get(DataKey) 獲取配置參數,現在已經不推薦使用了,這裏推薦使用 linkResolverContext.getOptions()的形式

註冊 Provider

自定義的Provider需要通過 HtmlRenderer.Builder.attributeProviderFactory 的方式註冊才能使用

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.itlangzi.com/test1 '測試1') [測試2](/test2 '測試2') [測試3](https://www.google.com/test3 '測試3')";

        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
            ));
        }));
    }
}

結果

result

碼磚不易,多多關注

原文鏈接 IT浪子の博客 > flexmark-java markdown給鏈接添加target屬性

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章