正则表达式应用之将markdown文档转换成html文档

Posted 二木成林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正则表达式应用之将markdown文档转换成html文档相关的知识,希望对你有一定的参考价值。

概述

正则表达式有一重要功能就是替换。

这种替换就可以应用到markdown文档上,markdown是一种文本格式,而可以将其转换成对应的html格式然后使用浏览器进行访问。

## 二级标题是markdown格式的标题形式,而<h2>二级标题</h2>则是html格式的标题形式,我们可以通过正则表达式来匹配到markdown文档中的标题,然后提取其中除了markdown特殊符号之外的标题内容,然后将标题内容放到html标签中也就实现了转换。

原理

关于markdown与html文本的转换关系及能匹配markdown的正则表达式匹配如下表:

名称markdown正则表达式html备注
粗体**粗体**__粗体__([\\*_]{2})(.*?)\\1<b>粗体</b>行内匹配
斜体*斜体*_斜体_(?<![\\*_])(\\*|_)([^\\*_]+?)\\1<i>斜体</i>行内匹配
删除线~~删除线~~(~~)(.*?)\\1<del>删除线</del>行内匹配
超链接[百度](http://www.baidu.com/)(?<!!)\\[(.*?)\\]\\((.*?)\\)<a href="http://www.baidu.com">百度</a>行内匹配
行内代码`int num=10;`(?!<`)(`)([^`]+?)`(?!`)<code>int num=10;</code>行内匹配
标题# 一级标题^(#{1,6})(.*)<h1>一级标题</h1>单行匹配
图片![图片名字](http://abc.com/star.jpg)^\\!\\[(.*?)\\]\\((.*?)\\)$<img src="http://abc.com/star.jpg" alt="图片名字" />单行匹配
分隔线***---___^(*|-|_){3}$<hr />单行匹配
无序列表- A
- B
- C
^([-\\+\\*]) (.*)<ul>
    <li>A</li>
    <li>B</li>
    <li>C</li>
</ul>
单行匹配,多行作用
有序列表1. A
2. B
3. C
^([\\d+])\\.\\s(.*)<ol>
    <li>A</li>
    <li>B</li>
    <li>C</li>
</ol>
单行匹配,多行作用
简单引用> 引用内容
> 引用内容
^([>] )(.*)<blockquote>引用内容</blockquote>单行匹配,多行作用
表格行|表头1|表头2|
|--|--|
|列1|列2|
^(\\|)(.*?)\\|$<table><tr><th>标题1</th><th>标题1</th></tr><tr><td>列1</td><td>列2</td></tr></table>单行匹配,多行作用
代码块```java
int num=10;
```
({3})(\\w+)([\\s\\S]\\*?\\|[\\w\\W]\\*?\\|[\\d\\D]*?)\\1 |int num=10;`跨行匹配
代码块开头```java^[`]{3}\\w+
代码块结尾```^[`]{3}$

可以将markdown中的标记分为如下四种情况:

  • 第一种:行内匹配。即在每一行中的某段文本可能匹配,如粗体、斜体、删除线、超链接、行内代码。处理方法是在读取每一行的时候直接使用正则表达式进行替换,注意,一行内可能会有多次行内匹配。
  • 第二种:单行匹配。即一整行完全匹配,如标题、图片、分隔线。处理方法是在读取每一行的时候直接使用正则表达式进行替换,注意,一行只会匹配一次。
  • 第三种:单行匹配,多行作用。即一整行完全匹配并且连续行都是这种格式,如无序列表、有序列表、简单引用、表格。处理方法是对连续行进行处理。
  • 第四种:跨行匹配。即多行匹配,如代码块。处理方法是直接对整个字符串使用正则表达式进行替换。

所以都是使用正则表达式进行行替换的。

实现

代码经过几经修改后,如下:

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>将markdown中的标记分为如下四种情况:</p>
 * <ul>
 *     <li>第一种:行内匹配。即在每一行中的某段文本可能匹配,如粗体、斜体、删除线、超链接、行内代码。处理方法是在读取每一行的时候直接使用正则表达式进行替换,注意,一行内可能会有多次行内匹配。</li>
 *     <li>第二种:单行匹配。即一整行完全匹配,如标题、图片、分隔线。处理方法是在读取每一行的时候直接使用正则表达式进行替换,注意,一行只会匹配一次。</li>
 *     <li>第三种:单行匹配,多行作用。即一整行完全匹配并且连续行都是这种格式,如无序列表、有序列表、简单引用、表格。处理方法是对连续行进行处理。</li>
 *     <li>第四种:跨行匹配。即多行匹配,如代码块。处理方法是直接对整个字符串使用正则表达式进行替换。</li>
 * </ul>
 *
 * @author lcl100
 * @create 2021-11-06 20:05
 * @desc 将markdown文件转换html文件
 */
public class MarkdownToHtml {

    // 行内匹配,粗体正则
    private final static String MARKDOWN_BOLD_REGEXP = "([\\\\*_]{2})(.*?)\\\\1";
    // 行内匹配,斜体正则
    private final static String MARKDOWN_ITALIC_REGEXP = "(?<![\\\\*_])(\\\\*|_)([^\\\\*_]+?)\\\\1";// 图片超链接中可能有"_"下划线,会导致被当作斜体处理,无法显示,所以添加"\\b"
    // 行内匹配,删除线正则,匹配 ~~red~~
    private final static String MARKDOWN_DELETE_LINE_REGEXP = "(~~)(.*?)\\\\1";
    // 行内匹配,普通链接正则,匹配 []()
    private final static String MARKDOWN_SIMPLE_LINK_REGEXP = "(?<!!)\\\\[(.*?)\\\\]\\\\((.*?)\\\\)";
    // 行内匹配,行内代码正则,匹配 ``
    private final static String MARKDOWN_LINE_CODE_REGEXP = "(?!<`)(`)([^`]+?)`(?!`)";

    // 单行匹配,标题正则
    private final static String MARKDOWN_TITLE_REGEXP = "^(#{1,6})(.*)";
    // 单行匹配,图片链接正则,匹配 ![]()
    private final static String MARKDOWN_IMAGE_LINK_REGEXP = "^\\\\!\\\\[(.*?)\\\\]\\\\((.*?)\\\\)$";
    // 单行匹配,分割线正则,匹配 ***或---或___
    private final static String MARKDOWN_SEPARATE_LINE_REGEXP = "^(\\\\*|-|_){3}$";

    // 单行匹配,多行作用,无序列表正则
    private final static String MARKDOWN_UNORDERED_LIST_REGEXP = "^([-\\\\+\\\\*]) (.*)";
    // 单行匹配,多行作用,有序列表正则
    private final static String MARKDOWN_ORDERED_LIST_REGEXP = "^([\\\\d+])\\\\.\\\\s(.*)";
    // 单行匹配,多行作用,简单引用正则
    private final static String MARKDOWN_SIMPLE_QUOTE_REGEXP = "^([>] ?)(.*)";
    // 单行匹配,多行作用,表格行正则,匹配 |表格列内容|表格列内容|
    private final static String MARKDOWN_TABLE_ROW_REGEXP = "^(\\\\|)(.*?)\\\\|$";

    // 跨行匹配,代码块正则,需要跨行匹配 ```java ```
    private final static String MARKDOWN_CODE_BLOCK_REGEXP = "(`{3})(\\\\w+)([\\\\s\\\\S]*?|[\\\\w\\\\W]*?|[\\\\d\\\\D]*?)\\\\1";// 匹配一整个代码块
    private final static String MARKDOWN_CODE_BLOCK_START_REGEXP = "^[`]{3}\\\\w+";// 匹配代码块的开头
    private final static String MARKDOWN_CODE_BLOCK_END_REGEXP = "^[`]{3}$";// 匹配代码块的结尾

    /**
     * 将markdown文件转换成html文件
     *
     * @param md   markdown文件路径
     * @param html html文件路径
     */
    public static void convert(String md, String html) {
        // 加载markdown文件中的所有非空白行
        List<String> lines = loadMd(md);

        // 处理单行匹配,多行作用
        String newLine = "";
        List<String> resultList = new ArrayList<String>();
        List<String> tempList = new ArrayList<String>();
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i);
            // 处理行内匹配
            Matcher boldMatcher = Pattern.compile(MARKDOWN_BOLD_REGEXP).matcher(line);
            if (boldMatcher.find()) {// 粗体
                line = replaceBold(line);
            }
            Matcher italicMatcher = Pattern.compile(MARKDOWN_ITALIC_REGEXP).matcher(line);
            if (italicMatcher.find()) {// 斜体
                line = replaceItalic(line);
            }
            Matcher deleteLineMatcher = Pattern.compile(MARKDOWN_DELETE_LINE_REGEXP).matcher(line);
            if (deleteLineMatcher.find()) {// 删除线
                line = replaceDeleteLine(line);
            }
            Matcher simpleLinkMatcher = Pattern.compile(MARKDOWN_SIMPLE_LINK_REGEXP).matcher(line);
            if (simpleLinkMatcher.find()) {// 普通链接
                line = replaceSimpleLink(line);
            }
            Matcher lineCodeMatcher = Pattern.compile(MARKDOWN_LINE_CODE_REGEXP).matcher(line);
            if (lineCodeMatcher.find()) {// 行内代码
                line = replaceLineCode(line);
            }
            // 处理单行匹配
            Matcher titleMatcher = Pattern.compile(MARKDOWN_TITLE_REGEXP).matcher(line);
            if (titleMatcher.find()) {// 标题行
                line = replaceTitle(line);
            }
            Matcher imageLinkMatcher = Pattern.compile(MARKDOWN_IMAGE_LINK_REGEXP).matcher(line);
            if (imageLinkMatcher.find()) {// 图片链接行,注意图片链接中如果匹配到斜体的"_"可能无法解析
                line = replaceImageLink(line);
            }
            Matcher separateLineMatcher = Pattern.compile(MARKDOWN_SEPARATE_LINE_REGEXP).matcher(line);
            if (separateLineMatcher.find()) {// 分隔线行
                line = replaceSeparateLine(line);
            }

            // 处理单行匹配,多行作用
            // 无序列表
            // 下面的代码就是将连续的无序列表行添加到List集合中,再调用相关方法进行处理
            tempList.clear();
            Matcher unorderedListMatcher = Pattern.compile(MARKDOWN_UNORDERED_LIST_REGEXP).matcher(line);
            while (unorderedListMatcher.find()) {
                tempList.add(line);
                i++;
                line = lines.get(i);
                unorderedListMatcher = Pattern.compile(MARKDOWN_UNORDERED_LIST_REGEXP).matcher(line);
            }
            resultList.add(replaceUnorderedList(tempList));
            // 有序列表
            tempList.clear();
            Matcher orderedListMatcher = Pattern.compile(MARKDOWN_ORDERED_LIST_REGEXP).matcher(line);
            while (orderedListMatcher.find()) {
                tempList.add(line);
                i++;
                line = lines.get(i);
                orderedListMatcher = Pattern.compile(MARKDOWN_ORDERED_LIST_REGEXP).matcher(line);
            }
            resultList.add(replaceOrderedList(tempList));
            // 简单引用,如果是空">"行则后面应该有空格,否则也无法匹配成功
            tempList.clear();
            Matcher simpleQuoteMatcher = Pattern.compile(MARKDOWN_SIMPLE_QUOTE_REGEXP).matcher(line);
            while (simpleQuoteMatcher.find()) {
                tempList.add(line);
                i++;
                line = lines.get(i);
                simpleQuoteMatcher = Pattern.compile(MARKDOWN_SIMPLE_QUOTE_REGEXP).matcher(line);
            }
            resultList.add(replaceSimpleQuote(tempList));
            // 表格行,注意每个表格行最后一个"|"的后面不能有空格,否则无法匹配成功
            tempList.clear();
            Matcher tableRowMatcher = Pattern.compile(MARKDOWN_TABLE_ROW_REGEXP).matcher(line);
            while (tableRowMatcher.find()) {
                tempList.add(line);
                i++;
                line = lines.get(i);
                tableRowMatcher = Pattern.compile(MARKDOWN_TABLE_ROW_REGEXP).matcher(line);
            }
            resultList.add(replaceTable(tempList));

            // 试图处理跨行代码
            tempList.clear();
            Matcher codeBlockStartMatcher = Pattern.compile(MARKDOWN_CODE_BLOCK_START_REGEXP).matcher(line);
            if (codeBlockStartMatcher.find()) {
                Matcher codeBlockEndMatcher = Pattern.compile(MARKDOWN_CODE_BLOCK_END_REGEXP).matcher(line);
                while (!codeBlockEndMatcher.find()) {
                    tempList.add(line);
                    i++;
                    line = lines.get(i);
                    codeBlockEndMatcher = Pattern.compile(MARKDOWN_CODE_BLOCK_END_REGEXP).matcher(line);
                }
                tempList.add(line);
                resultList.add(replaceCodeBlock(tempList));
                continue;
            }

            if (line.trim().length() > 0) {
                resultList.add("<p>" + line + "</p>");
            }
        }

        // 将lines集合中的所有行写入到字符串中
        StringBuilder htmlStr = new StringBuilder();
        // 添加html文档的头部和尾部
        htmlStr.append("<!DOCTYPE html>\\n" +
                "<html lang=\\"en\\">\\n" +
                "<head>\\n" +
                "    <meta charset=\\"UTF-8\\">\\n" +
                "    <title>Title</title>\\n" +
                "</head>\\n" +
                "<body>");
        for (String line : resultList) {
            htmlStr.append(line);
        }
        htmlStr.append("</body>\\n" +
                "</html>");

        // 将转换后的html结果写入文件
        writeHtml(html, htmlStr.toString());
    }

    /**
     * 将markdown格式的粗体文本转换成html格式的粗体字符串
     *
     * @param text markdown格式的粗体文本
     * @return html格式的粗体字符串
     */
    private static String replaceBold(String text) {
        Matcher boldMatcher = Pattern.compile(MARKDOWN_BOLD_REGEXP).matcher(text);
        if (boldMatcher.find()) {// 粗体
            text = boldMatcher.replaceAll("<b>$2</b>");
        }
        return text;
    }

    /**
     * 将markdown格式的斜体文本转换成html格式的斜体字符串
     *
     * @param text markdown格式的斜体文本
     * @return html格式的斜体字符串
     */
    private static String replaceItalic(String text) {
        Matcher italicMatcher = Pattern.compile(MARKDOWN_ITALIC_REGEXP).matcher(text);
        if (italicMatcher.find()) {// 斜体
            text = italicMatcher.replaceAll("<i>$2</i>");
        }
        return text;
    }

    /**
     * 将markdown格式的删除线文本转换成html格式的删除线字符串
     *
     * @param text markdown格式的删除线文本
     * @return html格式的删除线字符串
     */
    private static String replaceDeleteLine(String text) {
        Matcher deleteLineMatcher = Pattern.compile(MARKDOWN_DELETE_LINE_REGEXP).matcher(text);
        if (deleteLineMatcher.find()) {// 删除线
            text = deleteLineMatcher.replaceAll("<del>$2</del>");
        }
        return text;
    }

    /**
     * 将markdown格式的超链接文本转换成html格式的超链接字符串
     *
     * @param text markdown格式的超链接文本
     * @return html格式的超链接字符串
     */
    private static String replaceSimpleLink(String text) {
        Matcher simpleLinkMatcher = Pattern.compile(MARKDOWN_SIMPLE_LINK_REGEXP).matcher(text);
        if (simpleLinkMatcher.find()) {// 普通链接
            text = simpleLinkMatcher.replaceAll("<a href='$2'>$1</a>");
        }
        return text;
    }

    /**
     * 将markdown格式的行内代码文本转换成html格式的行内代码字符串
     *
     * @param text markdown格式的行内代码文本
     * @return html格式的行内代码字符串
     */
    private static String replaceLineCode(String text) {
        Matcher lineCodeMatcher = Pattern.compile(MARKDOWN_LINE_CODE_REGEXP).matcher(text);
        if (lineCodeMatcher.find()) {// 行内代码
            text = lineCodeMatcher.replaceAll("<code>$2</code>");
        }
        return text;
    }

    /**
     * 将markdown格式的标题行文本转换成html格式的标题行字符串
     *
     * @param text markdown格式的标题行文本
     * @return html格式的标题行字符串
     */
    private static String replaceTitle(String text) {
        Matcher titleMatcher = Pattern.compile(MARKDOWN_TITLE_REGEXP).matcher(text);
        if (titleMatcher.find()) {// 标题行
            String sign = titleMatcher.group(1);
            text = titleMatcher.replaceAll("<h" + sign.length() + ">$2</h" + sign.length() + ">");
        }
        return text;
    }

    /**
     * 将markdown格式的图片链接文本转换成html格式的图片链接字符串
     *
     * @param text markdown格式的图片链接文本
     * @return html格式的图片链接字符串
     */
    private static String replaceImageLink(String text) {
        Matcher imageLinkMatcher = Pattern.compile(MARKDOWN_IMAGE_LINK_REGEXP).matcher(text);
        if (imageLinkMatcher.find()) {// 图片链接行
            text = imageLinkMatcher.replaceAll("<img src='$2' title='$1' alt='$1' />");
        }
        return text;
    }

    /**
     * 将markdown格式的分隔线文本转换成html格式的分隔线字符串
     *
     * @param text markdown格式的分隔线文本
     * @return html格式的分隔线字符串
     */
    private static String replaceSeparateLine(String text) {
        Matcher s

以上是关于正则表达式应用之将markdown文档转换成html文档的主要内容,如果未能解决你的问题,请参考以下文章

将 Markdown 转换为 HTML 的正则表达式

oracle改进之将阿拉伯数字转换成中文数字

算法之将一个16进制的字符串转换成整数返回

Calibre软件教程之将epub格式转换成mobi格式的技巧

python实战技巧之将列表中整型的数据转换成字符型数据

如何利用 nbconvert将 IPYNB文档转换 Markdown文档?