JavaScript+Node.js写一款markdown解析器

目录
  • 1. 准备工作
  • 2. 处理图片&超链接
  • 3. 处理blockquote
  • 4. 处理标题
  • 5. 处理字体
  • 6. 处理代码块
  • 7. 处理列表
  • 8. 处理表格
  • 9. 调用方法

1. 准备工作

首先编写getHtml函数,传入markdown文本字符串,这里使用fs读取markdown文件内容,返回值是转换过后的字符串。

const fs = require('fs');

const source = fs.readFileSync('./test.md', 'utf-8');

const getHtml = (source) => {
    // 处理标题
    return source;
}

const result = getHtml(source);

console.log(result);

主要设计正则表达式和String.prototype.replace方法,replace接收的第一个参数可以是正则,第二个参数如果是函数那么返回值就是所替换的内容。

2. 处理图片&超链接

图片和超链接的语法很像,![图片](url),[超链接](url),使用正则匹配同时需要排除`。props会获取正则中的$,$1,$2。也就是匹配的字符整体,第一个括号内容,第二个括号内容。比如这里props[0]就是匹配到的完整内容,第四个参数props[3]是[]中的alt,第五个参数props[4]是链接地址。

const imageora = (source) => {
    return source.replace(/(`?)(!?)\[(.*)\]\((.+)\)/gi, (...props) => {
        switch (props[0].trim()[0]) {
            case '!': return `<a href="${props[4]}" rel="external nofollow"  alt="${props[3]}">${props[3]}</a>`;
            case '[': return `<img src="${props[4]}" alt="${props[3]}"/>`;
            default: return props[0];
        }
    });
}

const getHtml = (source) => {
    source = imageora(source);
    return source;
}

3. 处理blockquote

这里使用\x20匹配空格。如果匹配到内容,将文本props[3]放在blockquote标签返回就行了。

const block = (source) => {
    return source.replace(/(.*)(`?)\>\x20+(.+)/gi, (...props) => {
        switch (props[0].trim()[0]) {
            case '>': return `<blockquote>${props[3]}</blockquote>`;
            default: return props[0];
        }
    });
}

4. 处理标题

匹配必须以#开头,并且#的数量不能超过6,因为h6是最大的了,没有h7,最后props[2]是#后跟随的文本。

const formatTitle = (source) => {
    return source.replace(/(.*#+)\x20?(.*)/g, (...props) => {
        switch (props[0][0]) {
            case '#': if (props[1].length <= 6) {
                return `<h${props[1].length}>${props[2].trim()}</h${props[1].length}>`;
            };
            default: return props[0];
        }
    })
}

5. 处理字体

写的开始复杂了

const formatFont = (source) => {
    // 处理 ~ 包裹的文本
    source = source.replace(/([`\\]*\~{2})(.*?)\~{2}/g, (...props) => {
        switch (props[0].trim()[0]) {
            case '~': return `<del>${props[2]}</del>`;;
            default: return props[0];
        }
    });
    // 处理 * - 表示的换行
    source = source.replace(/([`\\]*)[* -]{3,}\n/g, (...props) => {
        switch (props[0].trim()[0]) {
            case '*': ;
            case '-': return `<hr />`;
            default: return props[0];
        }
    })
    // 处理***表示的加粗或者倾斜。
    source = source.replace(/([`\\]*\*{1,3})(.*?)(\*{1,3})/g, (...props) => {
        switch (props[0].trim()[0]) {
            case '*': if (props[1] === props[3]) {
                if (props[1].length === 1) {
                    return `<em>${props[2]}</em>`;;
                } else if (props[1].length === 2) {
                    return `<strong>${props[2]}</strong>`;;
                } else if (props[1].length === 3) {
                    return `<strong><em>${props[2]}</em></strong>`;;
                }
            };
            default: return props[0];
        }
    });
    return source;
}

6. 处理代码块

使用正则匹配使用`包裹的代码块,props[1]是开头`的数量,props[5]是结尾`的数量,必须相等才生效。

const pre = (source) => {
    source = source.replace(/([\\`]+)(\w+(\n))?([^!`]*?)(`+)/g, (...props) => {
        switch (props[0].trim()[0]) {
            case '`': if (props[1] === props[5]) {
                return `<pre>${props[3] || ''}${props[4]}</pre>`;
            };
            default: return props[0];
        }
    });
    return source;
}

7. 处理列表

这里只是处理了ul无序列表,写的同样很麻烦。主要我的思路是真复杂。而且bug肯定也不少。先匹配-+*加上空格,然后根据这一行前面的空格熟替换为ul。这样每一行都保证被ulli包裹。

第二步判断相邻ul之间相差的个数,如果相等则表示应该是同一个ul的li,替换掉</ul><ul>为空,如果后一个ul大于前一个ul,则表示后面有退格,新生成一个<ul>包裹退格后的li,如果是最后一个ul则补齐前面所有的</ul>。

const list = (source) => {
    source = source.replace(/.*?[\x20\t]*([\-\+\*]{1})\x20(.*)/g, (...props) => {
        if (/^[\t\x20\-\+\*]/.test(props[0])) {
            return props[0].replace(/([\t\x20]*)[\-\+\*]\x20(.*)/g, (...props) => {
                const len = props[1].length || '';
                return `<ul${len}><li>${props[2]}</li></ul${len}>`;
            })
        } else {
            return props[0];
        }
    });
    const set = new Set();
    source = source.replace(/<\/ul(\d*)>(\n<ul(\d*)>)?/g, (...props) => {
        set.add(props[1]);
        if (props[1] == props[3]) {
            return '';
        } else if (props[1] < props[3]) {
            return '<ul>';
        } else {
            const arr = [...set];
            const end = arr.indexOf(props[1]);
            let start = arr.indexOf(props[3]);
            if (start > 0) {
                return '</ul>'.repeat(end - start);
            } else {
                return '</ul>'.repeat(end + 1);
            }            
        }
    });
    return source.replace(/<(\/?)ul(\d*)>/g, '<$1ul>');
}

8. 处理表格

const table = (source) => {
    source = source.replace(/\|.*\|\n\|\s*-+\s*\|.*\|\n/g, (...props) => {
        let str = '<table><tr>';
        const data = props[0].split(/\n/)[0].split('|');
        for (let i = 1; i < data.length - 1; i++) {
            str += `<th>${data[i].trim()}</th>`
        }
        str += '<tr></table>';
        return str;
    });
    return formatTd(source);
}

const formatTd = (source) => {
    source = source.replace(/<\/table>\|.*\|\n/g, (...props) => {
        let str = '<tr>';
        const data = props[0].split('|');
        for (let i = 1; i < data.length - 1; i++) {
            str += `<td>${data[i].trim()}</td>`
        }
        str += '<tr></table>';
        return str;
    });
    if (source.includes('</table>|')) {
        return formatTd(source);
    }
    return source;
}

9. 调用方法

const getHtml = (source) => {
    source = imageora(source);
    source = block(source);
    source = formatTitle(source);
    source = formatFont(source);
    source = pre(source);
    source = list(source);
    source = table(source);
    return source;
}

const result = getHtml(source);

console.log(result);

到此这篇关于JavaScript+Node.js写一款markdown解析器的文章就介绍到这了,更多相关写一款markdown解析器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 实例分析浏览器中“JavaScript解析器”的工作原理

    浏览器在读取HTML文件的时候,只有当遇到<script>标签的时候,才会唤醒所谓的"JavaScript解析器"开始工作. JavaScript解析器工作步骤: 1."找一些东西": var. function. 参数:(也被称之为预解析) 备注:如果遇到重名分为以下两种情况: 遇到变量和函数重名了,只留下函数 遇到函数重名了,根据代码的上下文顺序,留下最后一个 2.逐行解读代码. 备注:表达式可以修改预解析的值 JS解析器在执行第一步预解析的时候,会

  • JavaScript+Node.js写一款markdown解析器

    目录 1. 准备工作 2. 处理图片&超链接 3. 处理blockquote 4. 处理标题 5. 处理字体 6. 处理代码块 7. 处理列表 8. 处理表格 9. 调用方法 1. 准备工作 首先编写getHtml函数,传入markdown文本字符串,这里使用fs读取markdown文件内容,返回值是转换过后的字符串. const fs = require('fs'); const source = fs.readFileSync('./test.md', 'utf-8'); const get

  • xtemplate node.js 的使用方法实例解析

    工程下安装XTemplate并使用它的方法实例说明: 1.安装xtpl 复制代码 代码如下: npm install xtpl xtemplate --save 2.在views目录添加test.xtpl文件,其内容为 this is {{title}}! 4.集成到Express中,只需要在app.js中,设置模板引擎即可 var print = require('./routes/print'); //此行代码放入app.js的require 声明代码段下边 app.set('view en

  • 三种Node.js写文件的方式

    本文分享了Node.js写文件的三种方式,具体内容和如下 1.通过管道流写文件 采用管道传输二进制流,可以实现自动管理流,可写流不必当心可读流流的过快而崩溃,适合大小文件传输(推荐) var readStream = fs.createReadStream(decodeURIComponent(root + filepath.pathname)); // 必须解码url readStream.pipe(res); // 管道传输 res.writeHead(200,{ 'Content-Type

  • 获取本机IP地址的实例(JavaScript / Node.js)

    --web 客户端JavaScript <!-- 调用方式 --> <body onload="checkCookie()"></body> function getYourIP() { const RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; if (RTCPeerConne

  • 详解用Node.js写一个简单的命令行工具

    本文介绍了用Node.js写一个简单的命令行工具,分享给大家,具体如下: 操作系统需要为Linux 1. 目标 在命令行输入自己写的命令,完成目标任务 命令行要求全局有效 命令行要求可以删除 命令行作用,生成一个文件,显示当前的日期 2. 代码部分 新建一个文件,命名为sherryFile 文件sherryFile的内容 介绍: 生成一个文件,文件内容为当前日期和创建者 #! /usr/bin/env node console.log('command start'); const fs = r

  • 使用Node.js写一个代码生成器的方法步骤

     背景 第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端 curd代码.之后也用过 CodeSmith , T4.目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操作. 自己写一个的原因是因为要集成到自己写的一个小工具中,而且使用 Node.js 这种动态脚本语言进行编写更加灵活. 原理 代码生成器的原理就是: 数据 + 模板 => 文件 . 数据 一般为数据库的表字段结构. 模板 的语法与使用的模板引擎有关. 使用模板引擎将 数据 和 模板 进行编译,编译

  • Node.js 中如何收集和解析命令行参数

    前言 在开发 CLI(Command Line Interface)工具的业务场景下,离不开命令行参数的收集和解析. 接下来,本文介绍如何收集和解析命令行参数. 收集命令行参数 在 Node.js 中,可以通过 process.argv 属性收集进程被启动时传入的命令行参数: // ./example/demo.js process.argv.slice(2); // 命令行执行如下命令 node ./example/demo.js --name=xiaoming --age=20 man //

  • unified如何处理markdown解析器详解

    目录 unified是什么 unified生态简介 工作原理 Parse Transform Stringify 牛刀小试 环境搭建 处理ESM类型包 最简用法 加载文档meta 一个实际使用例子: unified是什么 unified是用于文档处理的生态系统,核心包提供了文档处理的流程控制,具体功能由生态系统中各个插件提供.例如我们如果需要处理markdown,就需要使用markdown处理相关的插件.当然除了markdwon以外,还提供了处理HTML.JSX等的插件.其良好的扩展能力能让我们

  • JavaScript 用Node.js写Shell脚本[译]

    访问参数 你可以通过process.argv来访问到命令行参数,它是一个包含下列内容的数组: [ nodeBinary, script, arg0, arg1, ... ] 也就是说,第一个参数是从process.argv[2]开始的,你可以像下面这样遍历所有的参数: 复制代码 代码如下: process.argv.slice(2).forEach(function (fileName) { ... }); 如果你想对参数做更复杂的处理,可以看一下Node.js模块nomnom和optimist

  • 利用node.js写一个爬取知乎妹纸图的小爬虫

    前言 说起写node爬虫的原因,真是羞羞呀.一天,和往常一样,晚上吃过饭便刷起知乎来,首页便是推荐的你见过最漂亮的女生长什么样?,点进去各种漂亮的妹纸爆照啊!!!,看的我好想把这些好看的妹纸照片都存下来啊!一张张点击保存,就在第18张得时候,突然想起.我特么不是程序员么,这种手动草做的事,怎么能做,不行我不能丢程序员的脸了,于是便开始这次爬虫之旅. 原理 初入爬虫的坑,没有太多深奥的理论知识,要获取知乎上帖子中的一张图片,我把它归结为以下几步. 准备一个url(当然是诸如你见过最漂亮的女生长什么

随机推荐