CCF CSP 201703-3.Markdown

问题描述

  Markdown 是一种很流行的轻量级标记语言(lightweight markup language),广泛用于撰写带格式的文档。例如以下这段文本就是用 Markdown 的语法写成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Heading

## Sub-heading

Paragraphs are separated
by a blank line.

Text attribute _italic_.

Bullet list:

* apples
* oranges
* pears

A [link](http://example.com).

​ 这些用 Markdown 写成的文本,尽管本身是纯文本格式,然而读者可以很容易地看出它的文档结构。同时,还有很多工具可以自动把 Markdown 文本转换成 HTML 甚至 Word、PDF 等格式,取得更好的排版效果。例如上面这段文本通过转化得到的 HTML 代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h1>Heading</h1>

<h2>Sub-heading</h2>

<p>Paragraphs are separated
by a blank line.</p>

<p>Text attribute <em>italic</em>.</p>

<p>Bullet list:</p>

<ul>
<li>apples</li>
<li>oranges</li>
<li>pears</li>
</ul>

<p>A <a href="http://example.com">link</a>.</p>

​ 本题要求由你来编写一个 Markdown 的转换工具,完成 Markdown 文本到 HTML 代码的转换工作。简化起见,本题定义的 Markdown 语法规则和转换规则描述如下:
  ●区块:区块是文档的顶级结构。本题的 Markdown 语法有 3 种区块格式。在输入中,相邻两个区块之间用一个或多个空行分隔。输出时删除所有分隔区块的空行。
  ○段落:一般情况下,连续多行输入构成一个段落。段落的转换规则是在段落的第一行行首插入 <p>,在最后一行行末插入 </p>
  ○标题:每个标题区块只有一行,由若干个 # 开头,接着一个或多个空格,然后是标题内容,直到行末。# 的个数决定了标题的等级。转换时,# Heading 转换为 <h1>Heading</h1>## Heading 转换为 <h2>Heading</h2>,以此类推。标题等级最深为 6。
  ○无序列表:无序列表由若干行组成,每行由 * 开头,接着一个或多个空格,然后是列表项目的文字,直到行末。转换时,在最开始插入一行 <ul>,最后插入一行 </ul>;对于每行,* Item 转换为 <li>Item</li>。本题中的无序列表只有一层,不会出现缩进的情况。
  ●行内:对于区块中的内容,有以下两种行内结构。
  ○强调:_Text_ 转换为 <em>Text</em>。强调不会出现嵌套,每行中 _ 的个数一定是偶数,且不会连续相邻。注意 _Text_ 的前后不一定是空格字符。
  ○超级链接:[Text](Link) 转换为 <a href="Link">Text</a>。超级链接和强调可以相互嵌套,但每种格式不会超过一层。

输入格式

  输入由若干行组成,表示一个用本题规定的 Markdown 语法撰写的文档。

输出格式

  输出由若干行组成,表示输入的 Markdown 文档转换成产生的 HTML 代码。

样例输入

1
2
3
# Hello

Hello, world!

样例输出

1
2
<h1>Hello</h1>
<p>Hello, world!</p>

评测用例规模与约定

  本题的测试点满足以下条件:

  ●本题每个测试点的输入数据所包含的行数都不超过100,每行字符的个数(包括行末换行符)都不超过100。

  ●除了换行符之外,所有字符都是 ASCII 码 32 至 126 的可打印字符。

  ●每行行首和行末都不会出现空格字符。

  ●输入数据除了 Markdown 语法所需,内容中不会出现 #*_[]()<>& 这些字符。

  ●所有测试点均符合题目所规定的 Markdown 语法,你的程序不需要考虑语法错误的情况。

  每个测试点包含的语法规则如下表所示,其中“√”表示包含,“×”表示不包含。

测试点编号 段落 标题 无序列表 强调 超级链接
1 × × × ×
2 × × ×
3 × × ×
4 × × ×
5 × × ×
6 × ×
7 × ×
8 × ×
9 × ×
10

提示

  由于本题要将输入数据当做一个文本文件来处理,要逐行读取直到文件结束,C/C++、Java 语言的用户可以参考以下代码片段来读取输入内容。

C++语言

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string>

int main() {
std::string line;
while(std::getline(std::cin, line)) {
...
}
}

C语言

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

#define BUF_SS 101

int main(void) {
char buf[101];
while(fgets(buf, BUF_SS, stdin)) {
/* Processing code here */
}
return 0;
}

Java语言

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = null;
while (sc.hasNextLine()) {
line = sc.nextLine();
...
}
}
}

分析:

需要注意的地方:

1.在输入中,相邻两个区块之间用一个或多个空行分隔。输出时删除所有分隔区块的空行。

2.每行行首和行末都不会出现空格字符。因此,我们只需要处理标题#和无序列表*后的空格。

3.本题中的无序列表只有一层,不会出现缩进的情况。

4.强调不会出现嵌套,每行中 _ 的个数一定是偶数,且不会连续相邻。

5.超级链接和强调可以相互嵌套,但每种格式不会超过一层。

6.行内结构(超级链接和强调)在标题、无序列表和段落中均可以存在。

  • C++版
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
#include <iostream>
#include <string>
using namespace std;

// 去掉开头的空格
string trim(string str) {
int index = 0;
while (str[index] == ' ') {
index++;
}
return str.substr(index, str.size() - index);
}
// 处理强调
string parseEm(string str) {
while (true) {
int start = str.find("_");
if (start == string::npos) {
break;
}
str = str.replace(start, 1, "<em>");
int end = str.find("_");
str = str.replace(end, 1, "</em>");
}
return str;
}
// 处理超级链接
string parseLink(string str) {
while (true) {
int start = str.find("[");
if (start == string::npos) {
break;
}
int end = str.find("](", start + 1);
int brace = str.find(")", end + 2);
string text = str.substr(start + 1, end - start - 1);
string link = str.substr(end + 2, brace - end - 2);
str = str.substr(0, start) + "<a href=\"" + link + "\">" + text + "</a>"
+ str.substr(brace + 1, str.size() - brace - 1);
}
return str;
}
// 处理行内结构(强调和超级链接)
string parseInline(string line) {
return parseLink(parseEm(line));
}

int main() {
string line;
while (getline(cin, line)) {
if (line.empty()) {
continue;
}
// 处理标题
if (line[0] == '#') {
// 计算标题的等级
int level = 0;
while (level < line.size() && line[level] == '#') {
level++;
}
// 去掉开头的空格
line = trim(line.substr(level, line.size() - level));
// 生成标题
cout << "<h" << level << ">" << parseInline(line) << "</h" << level << ">" << endl;
continue;
}

// 处理无序列表
if (line[0] == '*') {
cout << "<ul>" << endl;
// 去掉开头的空格
line = trim(line.substr(1, line.size() - 1));
// 生成列表中的项
cout << "<li>" << parseInline(line) << "</li>" << endl;

while (getline(cin, line) && !line.empty()) {
line = trim(line.substr(1, line.size() - 1));
cout << "<li>" << parseInline(line) << "</li>" << endl;
}
cout << "</ul>" << endl;
continue;
}

// 处理段落
cout << "<p>" << parseInline(line);
while (getline(cin, line) && !line.empty()) {
cout << endl;
cout << parseInline(line);
}
cout << "</p>" << endl;
}
return 0;
}
  • Java版
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
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

StringBuilder sb = new StringBuilder();
String line = null;

while (scan.hasNextLine()) {
line = scan.nextLine();
if (line.isEmpty()) {
continue;
}
// 处理标题
if (line.charAt(0) == '#') {
// 计算标题的等级
int level = 0;
while (level < line.length() && line.charAt(level) == '#') {
level++;
}
sb.append(String.format("<h%d>%s</h%d>\n", level, parseInline(line.substring(level).trim()), level));
continue;
}

// 处理无序列表
if (line.charAt(0) == '*') {
sb.append("<ul>\n");
sb.append("<li>").append(parseInline(line.substring(1).trim())).append("</li>\n");
while (scan.hasNextLine() && !(line = scan.nextLine()).isEmpty()) {
sb.append("<li>").append(parseInline(line.substring(1).trim())).append("</li>\n");
}
sb.append("</ul>\n");
continue;
}

// 处理段落
sb.append("<p>").append(parseInline(line));
while (scan.hasNextLine() && !(line = scan.nextLine()).isEmpty()) {
sb.append('\n').append(parseInline(line));
}
sb.append("</p>\n");
}
scan.close();

System.out.println(sb.toString());
}

// 处理行内结构(强调和超级链接)
public static String parseInline(String line) {
return parseLink(parseEm(line));
}

// 处理强调
public static String parseEm(String line) {

StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile("_[^_]+_");
Matcher m = p.matcher(line);
while (m.find()) {
String str = m.group();
m.appendReplacement(sb, "<em>" + str.substring(1, str.length() - 1) + "</em>");
}
m.appendTail(sb);
return sb.toString();

}

// 处理超级链接
public static String parseLink(String line) {

StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile("\\[[^\\]]+\\]\\([^\\)]+\\)");
Matcher m = p.matcher(line);
while (m.find()) {
String str = m.group();
int index = str.indexOf(']');
String text = str.substring(1, index);
String link = str.substring(index + 2, str.length() - 1);
m.appendReplacement(sb, "<a href=\"" + link + "\">" + text + "</a>");
}
m.appendTail(sb);
return sb.toString();
}
}

----------本文结束感谢您的阅读----------
坚持原创技术分享,您的支持将鼓励我继续创作!