每日一题:Markdown 文档解析
介绍
Markdown 因为其简洁的语法大受欢迎,已经成为大家写博客或文档时必备的技能点,众多博客平台都提倡用户使用 Markdown 语法进行文章书写,然后再发布后,实时的将其转化为常规的 HTML 页面渲染。
本题需要在已提供的基础项目中,使用 Nodejs 实现简易的 Markdown 文档解析器。
准备
开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:
1 2 3 4 5 6 7
| ├── docs.md ├── images │ └── md.jpg ├── index.html └── js ├── index.js └── parse.js
|
其中:
index.html 是主页面。
images 是图片文件夹。
docs.md 是需要解析的 Markdown 文件。
js/index.js 是提供的工具脚本,用于快速验证代码结果。
js/parse.js 是需要补充的脚本文件。
注意:打开环境后发现缺少项目代码,请手动键入下述命令进行下载:
1 2
| 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> 标签:
- 对引用区块进行解析,Markdown 中使用
> 作为前缀,将其解析成为 <blockquote> 标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| > 引用区块1
> 多级引用区块2 > 多级引用区块2
<blockquote> <p>引用区块1</p> </blockquote>
<blockquote> <p>多级引用区块2</p> <p>多级引用区块2</p> </blockquote>
|
- 对无序列表进行解析,Markdown 中使用
* 或者 - 作为前缀,将其解析成为 <ul> 标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| * 无序列表 * 无序列表 * 无序列表
或者: - 无序列表 - 无序列表 - 无序列表
<ul> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> </ul>
|
- 对图片进行解析,Markdown 中使用
 表示,将其解析成为 <img> 标签:
1 2 3 4 5
| 
<img src="./images/md.jpg" alt="图片">
|
- 对文字效果进行解析,比如粗体效果,和行内代码块,将其分别解析成
<b> 和 code 标签:
1 2 3 4 5
| 这是**粗体**的效果文字,这是内嵌的`代码行`
这是<b>粗体</b>的效果文字,这是内嵌的<code>代码行</code>
|
在验证代码效果时,你可以在终端运行:
程序会将解析的结果输出到 index.html 文件中,然后通过浏览器查看输出的 index.html 是否符合解析要求(注意:程序不会实时的将结果更新到 index.html 文件中,在你的代码变更后,请重新执行上述命令)。
在题目所提供的数据的情况下,完成后的效果如下:

规定
- 请勿修改
js/parse.js 文件外的任何内容。
- 请严格按照考试步骤操作,切勿修改考试默认提供项目中的文件名称、文件夹路径、class 名、id 名、图片名等,以免造成无法判题通过。
判分标准
- 完成对分隔符的解析,得 5 分。
- 完成对引用区块的解析,得 5 分。
- 完成对图片,和文字效果的解析,得 5 分。
- 完成对无序列表的解析,得 10 分。
总通过次数: 50 | 总提交次数: 124 | 通过率: 40.3%
难度: 中等 标签: 蓝桥杯真题, 2023, 省赛, Web 前端, Node.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
| 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; 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>`) }
}
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; }
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(); };
|