实现一个简单的 markdown 转译器

2019-12-26  本文已影响0人  梦想成真213

实现一个 markdown 转译器,就是一行一行的解析规则,就像人眼睛看的那样来操作,一行一行的看,然后匹配规则,如果是无序列表这种多行的规则,就判断如果是无序列表,就继续往下看一行,直到规则匹配完,记录匹配的位置索引,继续向下看一行,最后将解析到所有的 html 规则放到数组里面,返回即可。这里首先是根据换行将每一行的记录到数组里面,根据索引,一行行向下看,循环里面的规则,直到索引等于数组的长度就退出循环。

主函数

本地写一个 markdown 文件,然后读取文件的内容,传给转译器进行转译;

const main = () => {
    const fs = require('fs');
    const path = require('path')
    const file = path.join(__dirname, './test.md');
    const source = fs.readFileSync(file, 'utf-8');
    console.log(MdtoHtml(source).join('\n'));
}
main();

MdtoHtml 就是实现的转译函数,这个test.md 的内容如下:

## 这是一个标题
### 无序列表
+ React
+ Vue
+ webpack
+ Antd
这一行文字里面有**加粗**
+ vue
`代码片段`

现在就先转译这几个规则吧,MdtoHtml函数只要是包装一下,为了能看到转译的效果,直接console.log

const MdtoHtml = (source) => {
    // 判断参数类型
    if(typeof source === 'undefined' || source === null){
        throw new Error('input parameter is udefined or null')
    }
    if(typeof source !== 'string'){
        throw new Error(`input parameter type is ${Object.prototype.toString.call(source)}, not expected string`)
    }
    // 处理所有的 \n, \t, \r 为 \n
    // markdown 转译器
   const htmls = transCompiler(source.replace(/\r\n | \r/, '\n'));
   console.log(htmls);
}

接下来transCompiler函数才是最主要的,处理拿到的每一行的规则数组。

const transCompiler = (source) => {
    const lines = source.split('\n');
    // 返回转译过的 html 标签
    return transToDocument(lines);
}
const transToDocument = (lines) => {
    // 记录转译的当前行索引
    let cur = 0;

    // 存储转译每一行的 html 标签
    let htmls = [];

    // 循环转译每一行的 md 规则,如果 cur === length,跳出循环,如果有匹配的规则就进行下一轮的循环;
    // 转译每一条 markdown 规则
    while(true){
        if(cur === lines.length){
            break;
        }

        // 当前行
        const line = lines[cur];

        // 处理标题
        if(block.titleReg.test(line)){
            const { index, html } = handleTitle(lines, cur);
            htmls.push(html);
            cur = index;
            continue;
        }

        // 转译无序列表
        if(block.ulReg.test(line)){
            const { index, html } = handleUlList(lines, cur);
            htmls.push(html);
            cur = index;
            continue;
        }

        // 转译单行
        const html = singleLine(line);
        htmls.push(html);
        cur++;
    }

    return htmls.join('\n');
}

每一个规则都独立写到一个函数中,最后返回处理的html字符串和索引即可,处理的规则根据区块元素和区段元素划分;

 // 区块元素匹配的正则
const block = {
    // 标题
    titleReg: /^(\#{1,6}) (.+)/,
    codeReg: /.*\`(.+)\`.*/,
    boldReg: /.*\*\*(.+)\*\*.*/,
    ulReg: /^\+ (.+)/,
}

// 区段元素
const section = {
}

处理标题的函数 handleTitle,拿到当前行,解析,索引往下加一行,表示这行处理完,继续看下一行的规则。

const handleTitle = (lines, cur) => {
    const [ , hash, content ] = block.titleReg.exec(lines[cur]);
    const tag = `h${hash.length}`;
    const html = `<${tag}>${content}</${tag}>`;
    return {
        index: cur + 1,
        html
    }
}

处理无序列表handleUlList,看当前行是否满足无序的正则,如果满足就继续向下看,索引加1,不满足就退出循环,然后处理匹配到的行,拼接成ul,返回新的索引,和 html 字符串。

const handleUlList = (lines, cur) => {
    let next = cur;
    while(true){
        next++;
        if(!block.ulReg.test(lines[next])){
            break;
        }
    }
    //转译拿到的 N 行
    let htmls = [];
    for(let i = cur; i < next; i++){
        const li = lines[i];
        const result = block.ulReg.exec(li);
        const html = `<li>${result[1]}</li>`
        htmls.push(html);
    }
    // 首尾加闭合标签
    htmls.unshift('<ul>');
    htmls.push('</ul>');
    return {
        index: next,
        html: htmls.join('\n')
    }
}

再就是处理单行的函数,如果单行里面有code 或加粗,直接替换:

const singleLine = (line) => {
    return line.replace(block.codeReg, (match, code) => {
        // 单行代码
        return `<code>${code}</code>`
    }).replace(block.boldReg, (match, bold) => {
        // 单行加粗
        return `<b>${bold}</b>`
    });
}

最后这个简单的 markdown 转译器就写完了,处理的规则很少,只领会精神,全部处理的话,非常庞大,以后慢慢加吧。运行转译器,转译之后的内容如下:

<h3>无序列表</h3>
<ul>
<li>React</li>
<li>Vue</li>
<li>webpack</li>
<li>Antd</li>
</ul>
<b>加粗</b>
<ul>
<li>vue</li>
</ul>
<code>代码片段</code>

github 地址:https://github.com/mxcz213/koa-test

分支切到 md_to_html,完整代码在 md_to_html/static/js/mdtohtml.js 里面。

上一篇 下一篇

猜你喜欢

热点阅读