每日一题:Markdown 文档解析
发表于:2024-12-10
字数统计:1831 字
预计阅读7分钟
介绍
Markdown 因为其简洁的语法大受欢迎,已经成为大家写博客或文档时必备的技能点,众多博客平台都提倡用户使用 Markdown 语法进行文章书写,然后再发布后,实时的将其转化为常规的 HTML 页面渲染。
本题需要在已提供的基础项目中,使用 Nodejs 实现简易的 Markdown 文档解析器。
准备
开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:
txt
├── docs.md
├── images
│ └── md.jpg
├── index.html
└── js
├── index.js
└── parse.js其中:
index.html是主页面。images是图片文件夹。docs.md是需要解析的 Markdown 文件。js/index.js是提供的工具脚本,用于快速验证代码结果。js/parse.js是需要补充的脚本文件。
注意:打开环境后发现缺少项目代码,请手动键入下述命令进行下载:
bash
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/18213/07.zip && unzip 07.zip && rm 07.zip目标
在 js/parse.js 中实现几种特定的 Markdown 语法解析,目前初始文件中已实现标题解析(即从 # 前缀转换为 <hn> 标签),请你继续完善该文件 TODO 部分,完成剩余语法解析操作,具体需求如下:
- 对分隔符进行解析,Markdown 中使用
---(三条及以上的短横线) 作为分隔符,将其解析成为<hr>标签:
html
<!-- Markdown -->
----
<!-- 对应 HTML -->
<hr>- 对引用区块进行解析,Markdown 中使用
>作为前缀,将其解析成为<blockquote>标签:
html
<!-- Markdown -->
> 引用区块1
> 多级引用区块2
> 多级引用区块2
<!-- 对应 HTML -->
<blockquote>
<p>引用区块1</p>
</blockquote>
<blockquote>
<p>多级引用区块2</p>
<p>多级引用区块2</p>
</blockquote>- 对无序列表进行解析,Markdown 中使用
*或者-作为前缀,将其解析成为<ul>标签:
html
<!-- Markdown -->
* 无序列表
* 无序列表
* 无序列表
或者:
- 无序列表
- 无序列表
- 无序列表
<!-- 对应 HTML -->
<ul>
<li>无序列表</li>
<li>无序列表</li>
<li>无序列表</li>
</ul>- 对图片进行解析,Markdown 中使用
表示,将其解析成为<img>标签:
html
<!-- Markdown -->

<!-- 对应 HTML -->
<img src="./images/md.jpg" alt="图片">- 对文字效果进行解析,比如粗体效果,和行内代码块,将其分别解析成
<b>和code标签:
html
<!-- Markdown -->
这是**粗体**的效果文字,这是内嵌的`代码行`
<!-- 对应 HTML -->
这是<b>粗体</b>的效果文字,这是内嵌的<code>代码行</code>在验证代码效果时,你可以在终端运行:
bash
node ./js/index.js程序会将解析的结果输出到 index.html 文件中,然后通过浏览器查看输出的 index.html 是否符合解析要求(注意:程序不会实时的将结果更新到 index.html 文件中,在你的代码变更后,请重新执行上述命令)。
在题目所提供的数据的情况下,完成后的效果如下:

规定
- 请勿修改
js/parse.js文件外的任何内容。 - 请严格按照考试步骤操作,切勿修改考试默认提供项目中的文件名称、文件夹路径、class 名、id 名、图片名等,以免造成无法判题通过。
判分标准
- 完成对分隔符的解析,得 5 分。
- 完成对引用区块的解析,得 5 分。
- 完成对图片,和文字效果的解析,得 5 分。
- 完成对无序列表的解析,得 10 分。
总通过次数: 50 | 总提交次数: 124 | 通过率: 40.3%
难度: 中等 标签: 蓝桥杯真题, 2023, 省赛, Web 前端, Node.js
js
class Parser {
constructor() {
this.heading = /^(#{1,6}\s+)/;
this.blockQuote = /^(\>\s+)/;
this.unorderedList = /^((\*|-){1}\s+)/;
this.image = /\!\[(.*?)\]\((.*?)\)/g;
this.strongText = /\*{2}(.*?)\*{2}/g;
this.codeLine = /\`{1}(.*?)\`{1}/g;
// TODO: 补充分割符正则
this.hr = /^\-{3,}/;
}
// 获取单行内容
parseLineText(lineText) {
this.lineText = lineText;
}
// 是否是空行
isEmptyLine() {
return this.lineText === "";
}
// 是否为符合标题规范
isHeading() {
return this.heading.test(this.lineText);
}
isHr() {
return this.hr.test(this.lineText)
}
isBlockQuote() {
return this.blockQuote.test(this.lineText)
}
isList() {
return this.unorderedList.test(this.lineText)
}
isImage() {
return this.image.test(this.lineText)
}
isNomalLine() {
return true
}
isStrong() {
return this.strongText.test(this.lineText)
}
isCode() {
return this.codeLine.test(this.lineText)
}
// 解析标题
parseHeading() {
const temp = this.lineText.split(" ");
const headingLevel = temp[0].length;
const title = temp[1].trim();
return `<h${headingLevel}>${title}</h${headingLevel}>`;
}
parseHr() {
return `<hr>`
}
parseBlockQuote() {
const temp = this.lineText.split(" ");
const content = temp[1].trim();
return `<p>${content}</p>`;
}
parseList() {
const temp = this.lineText.split(" ");
const content = temp[1].trim();
return `<li>${content}</li>`;
}
parseImage() {
const myReg = /^\!\[(\S+)\]\((\S+)\)/
const str = this.lineText
myReg.test(str)
return `<img src="${RegExp.$2}" alt="${RegExp.$1}">`
}
parseStrong() {
return this.lineText.replace(this.strongText, `<b>${RegExp.$1}</b>`)
}
parseCodeLine() {
return this.lineText.replace(this.codeLine, `<code>${RegExp.$1}</code>`)
}
/**
* TODO: 请完成剩余各种语法的解析
* 1. 完成对分隔符的解析
* 2. 完成对引用区块的解析
* 3. 完成对图片,和文字效果的解析
* 4. 完成对无序列表的解析
*/
}
class Reader {
constructor(text) {
//获取全部原始文本
this.text = text;
this.lines = this.getLines();
this.parser = new Parser();
}
runParser() {
let currentLine = 0;
let hasParsed = [];
while (!this.reachToEndLine(currentLine)) {
// 获取行文本
this.parser.parseLineText(this.getLineText(currentLine));
// 判断空白行
if (this.parser.isEmptyLine()) {
currentLine++;
continue;
}
if (this.parser.isHeading()) {
hasParsed.push(this.parser.parseHeading());
currentLine++;
continue;
}
// TODO: 请完成剩余各种语法的解析
if (this.parser.isHr()) {
hasParsed.push(this.parser.parseHr());
currentLine++;
continue;
}
if (this.parser.isBlockQuote()) {
const preLine = this.getLineText(currentLine - 1);
const nextLine = this.getLineText(currentLine + 1);
if (!this.parser.blockQuote.test(preLine)) {
hasParsed.push(`<blockquote>`)
}
hasParsed.push(this.parser.parseBlockQuote())
currentLine++
if (!this.parser.blockQuote.test(nextLine)) {
hasParsed.push(`</blockquote>`)
}
continue
}
if (this.parser.isList()) {
const preLine = this.getLineText(currentLine - 1);
const nextLine = this.getLineText(currentLine + 1);
if (!this.parser.unorderedList.test(preLine)) {
hasParsed.push(`<ul>`)
}
hasParsed.push(this.parser.parseList())
currentLine++
if (!this.parser.unorderedList.test(nextLine)) {
hasParsed.push(`</ul>`)
}
continue
}
if (this.parser.isImage()) {
hasParsed.push(this.parser.parseImage());
currentLine++;
continue;
}
if (this.parser.isNomalLine()) {
let lineText = this.getLineText(currentLine)
if (this.parser.isStrong()) {
lineText = lineText.replace(this.parser.strongText, '<b>$1</b>')
}
if (this.parser.isCode()) {
lineText = lineText.replace(this.parser.codeLine, '<code>$1</code>')
}
hasParsed.push(lineText)
currentLine++
continue
}
currentLine++;
}
return hasParsed.join("");
}
getLineText(lineNum) {
return this.lines[lineNum];
}
getLines() {
this.lines = this.text.split("\n");
return this.lines;
}
reachToEndLine(line) {
return line >= this.lines.length;
}
}
module.exports = function parseMarkdown(markdownContent) {
return new Reader(markdownContent).runParser();
};