C++ 重构 markdown–> HTML 的引擎
之前正好刚用python写了一个简单的scheme解释器,出于对于python中模式匹配和字符串处理的顺手,用python做了之前的那个转换引擎。
但代码结构自己是很不满意的,但对于python的了解程度导致我动手之后可以完成功能,但实现的方式不太自然。
所有的问题最后都归结于一处,最开始的时候将字符串按照空格进行了分割,变为list,这一步其实是没有道理的,而且将字符串的很多信息给缩减了,当前也带来了一些方便,但是实在是不可取,导致对于一些上下文相关的结构必须反复的在前后挑选一个合适的时机来来回处理。
所以近来用C++重构了一下上面的引擎,比之前的看起来要结构清晰一些。
有几点体会:
1. C++ 的字符串操作确实比较差,但其实也勉强能用,比自己想的顺手一些;
2. C++ 11 的regex虽然不强,但用起来还是比较方便,一些简单的匹配任何完全可以胜任,并且支持分组匹配,比较方便,当然也可以用boost 中的regex;
3. 写的过程中,需要不断的调整代码的结构,比如刚开始想写分别写一下 斜体、粗体、删除线的处理,然后发现这三个是同一个模式,因此抽提为一个公共的函数,每种情况给与一个参数来判别;然后重复这个过程。
4. 和之前的python程序一样,最关键的一点在于区分哪些属于整行层面的,哪些处于token层次的,哪些属于上下文相关的,针对每一种有不同的处理方法。
首先先介绍几个辅助性的函数,前几个都是谓词函数,判断当前行是否是某个语法结构,最后的regex函数实际上是用regex来完成字符串中比较复杂的词法单元的替换:
链接、图片、上标
bool isUnorderedList(std::string &input){ if ((input[0] == '*' || input[0] == '+' || input[0] == '-') && input[1] == ' ') { return true; } else { return false; } } bool isQuote(std::string &input) { if (input[0] == '>' && input[1] == ' ') { return true; } else { return false; } } bool isOrderedList(std::string &input) { if (isdigit(input[0]) && input[1] == '.' && input[2] == ' ') { return true; } else { return false; } } bool isHerLine(std::string &input) { std::string::iterator i; for (i = input.begin(); i != input.end(); i++) { if (*i != '-') { break; } } if (i == input.end() && input.size() >= 3) { return true; } else { return false; } } codeType isCodeBlock(std::string &input){ codeType codeSym; if (input == "```python") { codeSym = codeType::PYTHON; } else if (input == "```c++") { codeSym = codeType::CPP; } else { codeSym = codeType::WRONG; } return codeSym; } // handle with link sup img std::string regex(std::string &input) { // \[*](*),link std::regex re_link("/\\[(.*)\\]\\((.*)\\)"); input=std::regex_replace(input, re_link, "<a href=\"$2\" target=\"_blank\">$1</a>"); std::regex re_sup("\\[(.*)\\]\\[(.*)\\]"); input = std::regex_replace(input, re_sup, "$1<sup>$2</sup>"); std::regex re_img("!\\[(.*)\\]\\((.*)\\)"); input = std::regex_replace(input, re_img, " <img src=\"$2\" align=\"middle\">"); return input; }
其中的codeType 定义在辅助头文件中
enum class stateBlock{ BEGIN, IN, END, }; enum class codeType { PYTHON, CPP, WRONG }; extern codeType code;
这两个enum class变量用于控制块结构和代码块结构的当前状态,解释一下,块结构是影响上下文的,如果进入块结构之后,那么词法层面的处理是不做的,并且所有的空白字符需要保留,针对代码块还需要做一些特殊的单词的着色处理。
下面是整个程序的核心部分,输入是一行的markdown文件,输出的是HTML文件
std::string parse(std::string &input) { std::string temp= input; if (input.empty()) { return ""; } if (state == stateBlock::IN) { std::regex re_blank("\\s"); std::regex re_lt("<"); temp = std::regex_replace(temp, re_blank, " "); temp = std::regex_replace(temp, re_lt, "<"); } // 块内 if (preHandle(temp) == 0) { temp = setColor(temp); temp.insert(0, "<p>"); temp+="</p>"; return temp; } currentLineOrder = false; temp = token(temp); temp = regex(temp); if (input[0] == '#'&& input[1] == ' ') { temp=handleTitle(input, 1); } else if (input[0] == '#'&& input[1] == '#' &&input[2]==' ') { temp=handleTitle(input, 2); } else if (input[0] == '#'&& input[1] == '#' &&input[2] == '#' && input[3]==' ') { temp=handleTitle(input, 3); } else if (isUnorderedList(input) ){ temp = handleUnorderedList(temp); } else if (isQuote(input)) { temp = handleQuote(temp); } else if (isOrderedList(input)) { temp = handleOrderedList(temp); } else if (isHerLine(input)) { temp = handleHerLine(); } else { temp += "</br>"; } closeOrderList(temp); return temp; }
其中每一个handle函数需要写一个单独的处理函数,其中某一些处理函数是调用某一个公共的函数,也就是根据类型进行具体的分派。
最后,用C++写的初衷还有一个,之前用python写完之后,整合到Qt中必须调用外部程序来实现,每次发现bug需要重新编译python程序并打包,再整合,很麻烦,因此这次用C++ 重写以后,可以很方便的整合到Qt 中,而且还实现了一个简单的同步滚动(目前是让左边的文本输入和右边的webView保持相同的比例来同步,实际上比较粗糙,而想要精细的话,可以在parse的过程中,记住每一行markdown程序对应右侧的几行,建立一个vector来存放这些值,这样就可以根据左侧的位置来让右侧滚动到相应的位置),或者可以在相应的位置插入锚,来实现跳转。
这段时间搞得比较琐碎,顺便也做了一个可视化的界面,算是放松了,接下来有空的话会把之前的scheme解释的函数应用和尾递归优化补起来。
顺便推荐一本书,《python 源码剖析》,可以对着python2.7的源码看,源码里面注释很多,还是比较好懂的,加上书中给你理清的大的框架,对理解一些数据结构的内存模型什么的有很大的帮助。