CCF CSP 201703-3 Markdown
問題描述
Markdown 是一種很流行的輕量級標記語言(lightweight markup language),廣泛用於撰寫帶格式的文檔。例如以下這段文本就是用 Markdown 的語法寫成的:
這些用 Markdown 寫成的文本,盡管本身是純文本格式,然而讀者可以很容易地看出它的文檔結構。同時,還有很多工具可以自動把 Markdown 文本轉換成 HTML 甚至 Word、PDF 等格式,取得更好的排版效果。例如上面這段文本通過轉化得到的 HTML 代碼如下所示:
本題要求由你來編寫一個 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 甚至 Word、PDF 等格式,取得更好的排版效果。例如上面這段文本通過轉化得到的 HTML 代碼如下所示:
本題要求由你來編寫一個 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 代碼。
樣例輸入
# Hello
Hello, world!
Hello, world!
樣例輸出
<h1>Hello</h1>
<p>Hello, world!</p>
<p>Hello, world!</p>
評測用例規模與約定
本題的測試點滿足以下條件:
●本題每個測試點的輸入數據所包含的行數都不超過100,每行字符的個數(包括行末換行符)都不超過100。
●除了換行符之外,所有字符都是 ASCII 碼 32 至 126 的可打印字符。
●每行行首和行末都不會出現空格字符。
●輸入數據除了 Markdown 語法所需,內容中不會出現 `#`、`*`、`_`、`[`、`]`、`(`、`)`、`<`、`>`、`&` 這些字符。
●所有測試點均符合題目所規定的 Markdown 語法,你的程序不需要考慮語法錯誤的情況。
每個測試點包含的語法規則如下表所示,其中“√”表示包含,“×”表示不包含。
●本題每個測試點的輸入數據所包含的行數都不超過100,每行字符的個數(包括行末換行符)都不超過100。
●除了換行符之外,所有字符都是 ASCII 碼 32 至 126 的可打印字符。
●每行行首和行末都不會出現空格字符。
●輸入數據除了 Markdown 語法所需,內容中不會出現 `#`、`*`、`_`、`[`、`]`、`(`、`)`、`<`、`>`、`&` 這些字符。
●所有測試點均符合題目所規定的 Markdown 語法,你的程序不需要考慮語法錯誤的情況。
每個測試點包含的語法規則如下表所示,其中“√”表示包含,“×”表示不包含。
| 測試點編號 | 段落 | 標題 | 無序列表 | 強調 | 超級鏈接 |
| 1 | √ | × | × | × | × |
| 2 | √ | √ | × | × | × |
| 3 | √ | × | √ | × | × |
| 4 | √ | × | × | √ | × |
| 5 | √ | × | × | × | √ |
| 6 | √ | √ | √ | × | × |
| 7 | √ | × | × | √ | √ |
| 8 | √ | √ | × | √ | × |
| 9 | √ | × | √ | × | √ |
| 10 | √ | √ | √ | √ | √ |
解析
根據題意,Markdown分為區塊結構和行內結構。
區塊結構的類型可以根據第一個符號進行辨別,區塊結構結束的可以通過空行來辨別。
行內結構與區塊結構的處理不同,則需要分析每一行的內容。
因此程序的兩個主題部分為分割區塊、處理行內結構。處理區塊結構的邏輯在main函數中,處理行內結構的邏輯在parseLine函數中。
每一種語法對應於一個函數。
代碼
C++
#include <iostream> #include <vector> #include <string> #include <cassert> using namespace std; string to_string(int i) { char buffer[10]; return string(itoa(i, buffer, 10)); } string parseEmphasize(string text) { string result; result += "<em>"; result += text; result += "</em>"; return result; } string parseLink(string text, string link) { string result; result += "<a href=\""; result += link; result += "\">"; result += text; result += "</a>"; return result; } string parseLine(string line) { string result; int i = 0; while(i<line.size()) { if(line[i]=='[') { string text, link; int j = i+1; while(line[j] != ']') j++; text = line.substr(i+1, j-i-1); i = j+1; assert(line[i]=='('); while(line[j]!=')') j++; link = line.substr(i+1, j-i-1); text = parseLine(text); link = parseLine(link); result += parseLink(text, link); i = j+1; } else if(line[i]=='_') { string text; int j = i+1; while(line[j]!='_') j++; text = line.substr(i+1, j-i-1); text = parseLine(text); result += parseEmphasize(text); i = j + 1; } else { result += line[i]; i++; } } return result; } string parseHeading(vector<string> &contents) { assert(contents.size()==1); int level = 1; int i = 0; string heading = parseLine(contents[0]); while(heading[i] == '#') i++; level = i; while(heading[i] == ' ') i++; string result; result += "<h"; result += to_string(level); result += '>'; result += heading.substr(i,-1); result += "</h"; result += to_string(level); result += ">\n"; return result; } string parseParagraph(vector<string> &contents) { string result; result += "<p>"; for(int i=0; i<contents.size(); i++) { result += parseLine(contents[i]); if(contents.size() != 0 && i != contents.size()-1) result += '\n'; } result += "</p>\n"; return result; } string parseUnorderedList(vector<string> &contents) { string result; result += "<ul>\n"; int j; for(int i=0; i<contents.size(); i++) { result += "<li>"; j = 1; while(contents[i][j] == ' ') j++; result += parseLine(contents[i].substr(j,-1)); result += "</li>\n"; } result += "</ul>\n"; return result; } int main() { string line; vector<string> contents; int blockType; // 0:empty, 1:paragraph, 2:heading, 3:unordered list string result; while(getline(cin, line) || contents.size()>0) { if(line.empty()) { if(blockType != 0) { switch(blockType) { case 1: result += parseParagraph(contents); break; case 2: result += parseHeading(contents); break; case 3: result += parseUnorderedList(contents); break; } contents.resize(0); blockType = 0; } } else if(line[0] == '#') { contents.push_back(line); blockType = 2; } else if(line[0] == '*') { contents.push_back(line); blockType = 3; } else { contents.push_back(line); blockType = 1; } line = ""; } cout << result; }
