第十次CCF認證 第三題 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 程式碼。 樣例輸入Hello
Hello, world! 樣例輸出
Hello
Hello, world!
評測用例規模與約定
本題的測試點滿足以下條件: ●本題每個測試點的輸入資料所包含的行數都不超過100,每行字元的個數(包括行末換行符)都不超過100。
●除了換行符之外,所有字元都是 ASCII 碼 32 至 126 的可列印字元。 ●每行行首和行末都不會出現空格字元。
●輸入資料除了 Markdown 語法所需,內容中不會出現
#
、*
、_
、[
、]
、(
、)
、<
、>
、&
這些字元。 ●所有測試點均符合題目所規定的
Markdown 語法,你的程式不需要考慮語法錯誤的情況。 每個測試點包含的語法規則如下表所示,其中“√”表示包含,“×”表示不包含。
思路:
理清題意,分情況思考,總共三種區塊型別,兩種行內處理,分5個模組來做,方便除錯執行。
三區塊型別分為三種state,程式初始化時,state=0表示不確定狀態,需根據輸入語句改變state值, 當輸入語句為空行時,state恢復為0
1,2,3分別對應標題、段落、無序列表。state確認之後,除非變為0,否則不變。變為0後輸出區塊型別結束符
##注意事項:
- 標題只有一行
- "在字串中表示是要加轉義符\的。
- 從檔案中讀入時,最後一個是沒有讀入的,所以就得自己做判斷,是否輸入已經結束,然後輸出結尾
程式碼:
#include<iostream>
#include<string>
using namespace std;
int state=0;//表示區塊型別
string str;
int turn();//判斷是否為空行,做段落和無序列表的結尾輸出
void first(); //判斷區塊型別,負責標題輸出和段落以及無序列表的開頭輸出
void block(); //開頭空格及符號處理
void header();//標題處理
void paragraph();//段落處理
void lists();//無序列表處理
void stress();//強調
int turn(){ //判斷是否為空行,做段落和無序列表的結尾輸出
if(str==""){
if(state==2) cout<<"</p>"<<endl;
else if(state==3) cout<<"</ul>"<<endl;
state=0;
return 1;
}
return 0;
}
void stress(string &str){
int pos,pos2,pos3;
pos2=str.find("<a");
pos3=str.find("</a");
pos=str.find("_");
if(pos>pos2 && pos<pos3) return;
while(pos>=0 ){
str.replace(pos,1,"<em>");
pos=str.find("_");
str.replace(pos,1,"</em>");
pos=str.find("_");
}
}
void link(){
string str1,str2,str3;
int pos,pos1,pos2,pos3;
pos=str.find("[");
pos1=str.find("]");
if(pos>=0){
pos2=str.find("(");
pos3=str.find(")");
str2=str.substr(pos2+1,pos3-pos2-1);
str3 =str.substr(pos+1,pos1-pos-1);
stress(str3);
str1 += "<a href=\"" + str2 + "\">" + str3 + "</a>";
str.replace(pos,pos3-pos+1,str1);
}
}
void block(){//開頭空格及符號處理
while((str[0]==' ' || str[0]=='#' || str[0]=='*')){
str.erase(0,1);
}
}
void header(){//標題處理
int i=0;
while(str[i]=='#'){
i++;
}
block();
link();
stress(str);
cout<<"<h"<<i<<">"<<str<<"</h"<<i<<">"<<endl;
}
void paragraph(){
block();
link();
stress(str);
cout<<endl<<str;
}
void lists(){
block();
link();
stress(str);
cout<<"<li>"<<str<<"</li>"<<endl;
}
void first(char c){ //判斷區塊型別,負責標題輸出和段落以及無序列表的開頭輸出
if(c=='#'){ //標題
header();
state=0;
}
else if(c=='*'){ //list
cout<<"<ul>"<<endl;
lists();
state=3;
}
else{ //文字
block();
link();
stress(str);
cout<<"<p>"<<str;
state=2;
}
return;
}
void deal(){
if(turn()==1) return;//先判斷輸入是否為空行,是的話做相應的段落和無序列表的結尾輸出,無相應的,則直接跳過
if(state==2){
paragraph();
}else if(state==3){
lists();
}
if(state==0)first(str[0]);//判斷是否有輸入,有輸入的話,輸出相應的開頭及輸出
}
int main(){
freopen("data.txt","r",stdin);
while(getline(cin,str)){
deal();
}
if(state==2) cout<<"</p>"<<endl;
else if(state==3) cout<<"</ul>"<<endl;
}
很遺憾這個程式碼只能拿90分,還有10分應該是卡在超連結和強調的相互巢狀處理上。拿別人滿分程式碼跟自己程式碼一起測了不少巢狀樣例,發現都沒什麼區別。不知道巢狀到底是怎樣的巢狀法。很難受