it编程 > 编程语言 > Javascript

原生JS实现HTML转Markdown功能

7人参与 2025-04-24 Javascript

之前因为一些需要,需要转换部分 html 标签成 markdown 格式,但是不知不觉就完善到一个相对完整的函数。

然后我就封装成了一个文件放在了 github ,也简单做了两个示例网页。

代码地址在 html2md

其实这类函数在 github 上有很多,但是或多或少都对 html 的还原支持的不够完善,比如 turndown.js 是最热门的,但却不支持表格的恢复,索性就自己做了一个。

其实之间的转换还挺复杂,需要考虑各个标签的优先级,做完又花了两天才完善到一定程度。

(不过需要提醒的是,safari 和 ios 上的浏览器不支持这个,因为它们对正则支持的不够完整。不过对于前者,可以使用chrome,对于后者,又压根无法复制出已封装了 html 的内容,所以也不需要考虑。)

代码的实现逻辑如下:

其中,最开始声明了一些数组变量,用于将一些转换过程中的中间产物进行储存。

然后 purehtml 这个变量就是整个加工过程中的原料,一直到最后。

首先,函数处理的入口是从 112 行 开始的。

第一步,删除 <style> 和 <script> 这两个标签及其内容。

第二步,将 pre 里的内容先存到数组里,然后用 ‘#precontent#’ 这个字符替换原来 pre 标签里的内容,我称这个操作为保护。因为后续会有很多复杂的内容,把 pre 保护了,就能保证它的原汁原味,因为 pre 本身就是代码,不能动。

第三步,和 pre 一样的 code ,为什么先 pre 再 code 呢?因为这两样东西有这样的包含关系,一般 pre 里可以有 code ,但 code 却没有 pre ,所以在考虑这样的逻辑后,决定这样储存。

第四步,就是在没有 pre 和 code 的干扰下,放心删除标签中其他没有用的属性,并将 a 和 img 的标签内容进行 “保护” ,以方便一会儿恢复。

第五步,就是替换一些简单的标签,什么标题啊,斜体啊,横线啊等等(还有将一些乱七八糟的标签直接删除).....最后依次处理表格和列表。

第六步,按照一定的规范,依次将上面 “保护” 的内容,进行恢复。

第七步,将最头部的空行删去。(我记得中间也曾检查多余的空行删去,不知道为什么没有了),然后转换完毕,将结果返回。

源码如下:

/**
 * 把 html 内容转化为 markdown 格式 v1.0
 * 
 * @author kohunglee
 * @param {string} htmldata 转换前的 html 
 * @return {string} 转化后的 markdown 源码
 */
function html2md(htmldata){
    codecontent     = new array  // code标签数据
    precontent      = new array  // pre标签数据
    tablecontent    = new array  // table标签数据
    olcontent       = new array  // ol标签数据
    imgcontent      = new array  // img标签数据
    acontent        = new array  // a标签数据
    let purehtml    = htmldata
 
    // 源代码
    console.log("转换前的源码:" + purehtml)
 
    // 函数:删去html标签
    function clearhtmltag(sourcedata = ''){  
        return sourcedata.replace(/\<[\s\s]*?\>/g,'')
    }
 
    // 复原ol标签
    function olrecover(oldata = ''){  
        let result = oldata
        let num = oldata.match(/\<li\>/ig).length
        for(let i = 1; i <= num; i++){
            let line = '[~wrap]'
            if(i == 1) line = '[~wrap][~wrap]'
            result = result.replace(/\<li\>/i, line + i + '. ')
        }
        result = result.replace(/\<\/li\>/, '')
        return result
    }
 
    // 函数:复原img标签
    function imgrecover(imghtml = ''){  
        let imgsrc,imgtit,imgalt,result
        imgsrc     = imghtml.match(/(?<=src=['"])[\s\s]*?(?=['"])/i)
        imgtit     = imghtml.match(/(?<=title=['"])[\s\s]*?(?=['"])/i)
        imgalt     = imghtml.match(/(?<=alt=['"])[\s\s]*?(?=['"])/i)
 
        imgtit = (imgtit != null) ? ` "${imgtit}"` : ' '
        imgalt = (imgalt != 'null') ? imgalt : " "
        result = `![${imgalt}](${imgsrc}${imgtit})`
        return result
    }
 
    // 函数:复原a标签
    function arecover(adata = ''){  
        let ahref = '' + adata.match(/(?<=href=['"])[\s\s]*?(?=['"])/i)
        let atit  = '' + adata.match(/(?<=title=['"])[\s\s]*?(?=['"])/i)
        let atext = '' + adata.match(/(?<=\<a\s*[^\>]*?\>)[\s\s]*?(?=<\/a>)/i)
 
        let aimg = adata.match(/<img\s*[^\>]*?\>[^]*?(<\/img>)?/i)
        let aimgsrc,aimgtit,aimgalt
 
        atit = (atit != 'null') ? ` "${atit}"` : ' '
        atext = clearhtmltag(atext)
        let result = `[${atext}](${ahref}${atit})`
        
        if(aimg != null){  // 函数:如果发现图片,则更换为图片显示模式
            aimgsrc     = aimg[0].match(/(?<=src=['"])[\s\s]*?(?=['"])/i)
            aimgtit     = aimg[0].match(/(?<=title=['"])[\s\s]*?(?=['"])/i)
            aimgalt     = aimg[0].match(/(?<=alt=['"])[\s\s]*?(?=['"])/i)
 
            aimgtit = (aimgtit != null) ? ` "${aimgtit}"` : ' '
            aimgalt = (aimgalt != 'null') ? aimgalt : " "
            result = `[![${aimgalt}](${aimgsrc}${aimgtit})](${ahref}${atit})`
        }
        return result
    }
 
    // 函数:复原table标签
    function tablerecover(tabledata = null){  
        if(tabledata[0] == null){  // 如果不存在 th 标签,则默认表格为一层
            let result = ''
            let colnum = tabledata[1].length
 
            for(let i = 0; i < colnum; i++){
            result += `|${clearhtmltag(tabledata[1][i])}`
            }
            result += `|[~wrap]`
            for(let j = 0; j < colnum; j++){
                result += `| :------------: `
            }
            result += `|[~wrap]`
            return result
        }
        let colnum = tabledata[0].length  // 如果存在 th 标签,则按 th 的格数来构建整个表格
        let result = ''
 
        for(let i = 0; i < colnum; i++){
            result += `|${clearhtmltag(tabledata[0][i])}`
        }
        result += `|[~wrap]`
        for(let j = 0; j < colnum; j++){
            result += `| :------------: `
        }
        result += `|[~wrap]`
        for(let k = 0; k < tabledata[1].length;){
            for(let z = 0; z < colnum; z++,k++){
                result += `|${clearhtmltag(tabledata[1][k])}`
            }
            result += `|[~wrap]`
        }
        return result+`[~wrap]`
    }
    // 去掉样式和脚本极其内容
    purehtml = purehtml.replace(/<style\s*[^\>]*?\>[^]*?<\/style>/ig,'').replace(/<script\s*[^\>]*?\>[^]*?<\/script>/ig,'')
 
    // 储存pre的内容,并替换<pre>中的内容
    precontent = purehtml.match(/<pre\s*[^\>]*?\>[^]*?<\/pre>/ig)
    purehtml = purehtml.replace(/(?<=\<pre\s*[^\>]*?\>)[\s\s]*?(?=<\/pre>)/ig,'`#precontent#`')
 
    // 储存code的内容,并替换<code>中的内容
    codecontent = purehtml.match(/(?<=\<code\s*[^\>]*?\>)[\s\s]*?(?=<\/code>)/ig)
    purehtml = purehtml.replace(/(?<=\<code\s*[^\>]*?\>)[\s\s]*?(?=<\/code>)/ig,'`#codecontent#`')
 
    // 储存a的内容,并替换<a>中的内容
    acontent = purehtml.match(/<a\s*[^\>]*?\>[^]*?<\/a>/ig)
    purehtml = purehtml.replace(/<a\s*[^\>]*?\>[^]*?<\/a>/ig,'`#acontent#`')
 
    // 储存img的内容,并替换<img>中的内容
    imgcontent = purehtml.match(/<img\s*[^\>]*?\>[^]*?(<\/img>)?/ig)
    purehtml = purehtml.replace(/<img\s*[^\>]*?\>[^]*?(<\/img>)?/ig,'`#imgcontent#`')
 
    // 获取纯净(无属性)的 html
    purehtml = purehtml.replace(/(?<=\<[a-za-z0-9]*)\s.*?(?=\>)/g,'')  
 
    // 标题:标获取<h1><h2>...数据,并替换
    purehtml = purehtml.replace(/<h1>/ig,'[~wrap]# ').replace(/<\/h1>/ig,'[~wrap][~wrap]')
                        .replace(/<h2>/ig,'[~wrap]## ').replace(/<\/h2>/ig,'[~wrap][~wrap]')
                        .replace(/<h3>/ig,'[~wrap]### ').replace(/<\/h3>/ig,'[~wrap][~wrap]')
                        .replace(/<h4>/ig,'[~wrap]#### ').replace(/<\/h4>/ig,'[~wrap][~wrap]')
                        .replace(/<h5>/ig,'[~wrap]##### ').replace(/<\/h5>/ig,'[~wrap][~wrap]')
                        .replace(/<h6>/ig,'[~wrap]###### ').replace(/<\/h6>/ig,'[~wrap][~wrap]')
 
    // 段落:处理一些常用的结构标签
    purehtml = purehtml.replace(/(<br>)/ig,'[~wrap]').replace(/(<\/p>)|(<br\/>)|(<\/div>)/ig,'[~wrap][~wrap]')
                       .replace(/(<meta>)|(<span>)|(<p>)|(<div>)/ig,'').replace(/<\/span>/ig,'')
 
    // 粗体:替换<b><strong>
    purehtml = purehtml.replace(/(<b>)|(<strong>)/ig,'**').replace(/(<\/b>)|(<\/strong>)/ig,'**')
 
    // 斜体:替换<i><em><abbr><dfn><cite><address>
    purehtml = purehtml.replace(/(<i>)|(<em>)|(<abbr>)|(<dfn>)|(<cite>)|(<address>)/ig,'*').replace(/(<\/i>)|(<\/em>)|(<\/abbr>)|(<\/dfn>)|(<\/cite>)|(<\/address>)/ig,'*')
 
    // 删除线:替换<del>
    purehtml = purehtml.replace(/\<del\>/ig,'~~').replace(/\<\/del\>/ig,'~~')
 
    // 引用:替换<blockquote>
    purehtml = purehtml.replace(/\<blockquote\>/ig,'[~wrap][~wrap]> ').replace(/\<\/blockquote\>/ig,'[~wrap][~wrap]')
 
    // 水平线:替换<hr>
    purehtml = purehtml.replace(/\<hr\>/ig,'[~wrap][~wrap]------[~wrap][~wrap]')
 
    // 表格 <table>,得到数据,删除标签,然后逐层分析储存,最终根据结果生成
    tablecontent = purehtml.match(/(?<=\<table\s*[^\>]*?\>)[\s\s]*?(?=<\/table>)/ig)
    purehtml = purehtml.replace(/<table\s*[^\>]*?\>[^]*?<\/table>/ig,'`#tablecontent#`')
    if(tablecontent !== null){  // 分析储存
        tbodycontent = new array
        for(let i = 0; i < tablecontent.length; i++){
            tbodycontent[i] = new array  // tbodycontent[i]的第一个数据是thead数据,第二个是tbody的数据
            tbodycontent[i].push(tablecontent[i].match(/(?<=\<th>)[\s\s]*?(?=<\/th?>)/ig))
            tbodycontent[i].push(tablecontent[i].match(/(?<=\<td>)[\s\s]*?(?=<\/td?>)/ig))
        }
    }
    if(typeof tbodycontent !== "undefined"){  // 替换
        for(let i = 0; i < tbodycontent.length; i++){
            let tabletext = tablerecover(tbodycontent[i])
            purehtml = purehtml.replace(/\`\#tablecontent\#\`/i,tabletext)
        }
    }
    
    // 有序列表<ol>的<li>,储存ol的内容,并循环恢复ol中的内容
    olcontent = purehtml.match(/(?<=\<ol\s*[^\>]*?\>)[\s\s]*?(?=<\/ol>)/ig)
    purehtml = purehtml.replace(/(?<=\<ol\s*[^\>]*?\>)[\s\s]*?(?=<\/ol>)/ig,'`#olcontent#`')
    if(olcontent !== null){
        for(let k = 0; k < olcontent.length; k++){
            let oltext = olrecover(olcontent[k])
            purehtml = purehtml.replace(/\`\#olcontent\#\`/i,clearhtmltag(oltext))
        }
    }
 
    // 无序列表<ul>的<li>,以及<dd>,直接替换
    purehtml = purehtml.replace(/(<li>)|(<dd>)/ig,'[~wrap] - ').replace(/(<\/li>)|(<\/dd>)/ig,'[~wrap][~wrap]')
 
    // 处理完列表后,将 <lu>、<\lu>、<ol>、<\ol> 处理
    purehtml = purehtml.replace(/(<ul>)|(<ol>)/ig,'').replace(/(<\/ul>)|(<\/ol>)/ig,'[~wrap][~wrap]')
 
    // 先恢复 img ,再恢复 a
    if(imgcontent !== null){
        for(let i = 0; i < imgcontent.length; i++){
            let imgtext = imgrecover(imgcontent[i])
            purehtml = purehtml.replace(/\`\#imgcontent\#\`/i,imgtext)
        }
    }
 
    // 恢复 a
    if(acontent !== null){
        for(let k = 0; k < acontent.length; k++){
            let atext = arecover(acontent[k])
            purehtml = purehtml.replace(/\`\#acontent\#\`/i,atext)
        }
    }
 
    // 换行处理,1.替换 [~wrap] 为 ‘\n'   2.首行换行删去。   3.将其他过长的换行删去。
    purehtml = purehtml.replace(/\\[\~wrap\\]/ig,'\n')
                       .replace(/\n{3,}/g,'\n\n')
 
    // 代码 <code> ,根据上面的数组恢复code,然后将code替换
    if(codecontent !== null){
        for(let i = 0; i < codecontent.length; i++){
            purehtml = purehtml.replace(/\`\#codecontent\#\`/i,clearhtmltag(codecontent[i]))
        }
    }
    purehtml = purehtml.replace(/\<code\>/ig,' ` ').replace(/\<\/code\>/ig,' ` ')
 
    // 代码 <pre> ,恢复pre,然后将pre替换
    if(precontent !== null){
        for(let k = 0; k < precontent.length; k++){
            let prelanguage = precontent[k].match(/(?<=language-).*?(?=[\s'"])/i)
            let pretext = clearhtmltag(precontent[k])
            pretext = pretext.replace(/^1\n2\n(\d+\n)*/,'')  // 去掉行数
 
            prelanguage = (prelanguage != null && prelanguage[0] != 'undefined') ? prelanguage[0] + '\n' : '\n'
            purehtml = purehtml.replace(/\`\#precontent\#\`/i,prelanguage + pretext)
        }
    }
    purehtml = purehtml.replace(/\<pre\>/ig,'```').replace(/\<\/pre\>/ig,'\n```\n')
 
    // 删去其余的html标签,还原预文本代码中的 '<' 和 '>'
    purehtml = clearhtmltag(purehtml)
    purehtml = purehtml.replace(/\&lt\;/ig,'<').replace(/\&gt\;/ig,'>')
 
    // 删去头部的空行
    purehtml = purehtml.replace(/^\n{1,}/i,'')
 
    return purehtml
}

到此这篇关于原生js实现html转markdown功能的文章就介绍到这了,更多相关js html转markdown内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

React使用Redux Toolkit的方法示例

04-24

Javascript中如何循环(常用方法推荐)

04-24

vue实现在线进制转换功能

04-24

JavaScript中常见的Polyfill示例详解

04-24

vue实现自定义颜色选择器

04-24

JavaScript随机数生成各种技巧及实例代码

04-24

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论