正则表达式应用之将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文档的主要内容,如果未能解决你的问题,请参考以下文章