• 设为首页
  • 收藏本站
  • 积分充值
  • VIP赞助
  • 手机版
  • 微博
  • 微信
    微信公众号 添加方式:
    1:搜索微信号(888888
    2:扫描左侧二维码
  • 快捷导航
    福建二哥 门户 查看主题

    原生JS实现HTML转Markdown功能

    发布者: 404号房间 | 发布时间: 2025-6-16 07:37| 查看数: 55| 评论数: 0|帖子模式

    之前因为一些需要,需要转换部分 HTML 标签成 markdown 格式,但是不知不觉就完善到一个相对完整的函数。
    然后我就封装成了一个文件放在了 github ,也简单做了两个示例网页。
    代码地址在 html2md

    其实这类函数在 github 上有很多,但是或多或少都对 HTML 的还原支持的不够完善,比如 turndown.js 是最热门的,但却不支持表格的恢复,索性就自己做了一个。
    其实之间的转换还挺复杂,需要考虑各个标签的优先级,做完又花了两天才完善到一定程度。
    (不过需要提醒的是,Safari 和 iOS 上的浏览器不支持这个,因为它们对正则支持的不够完整。不过对于前者,可以使用Chrome,对于后者,又压根无法复制出已封装了 HTML 的内容,所以也不需要考虑。)
    代码的实现逻辑如下:
    其中,最开始声明了一些数组变量,用于将一些转换过程中的中间产物进行储存。
    然后
    1. pureHtml
    复制代码
    这个变量就是整个加工过程中的原料,一直到最后。
    首先,函数处理的入口是从 112 行 开始的。
    第一步,删除
    1. <style>
    复制代码
    1. <script>
    复制代码
    这两个标签及其内容。
    第二步,将 pre 里的内容先存到数组里,然后用
    1. ‘#preContent#’
    复制代码
    这个字符替换原来 pre 标签里的内容,我称这个操作为保护。因为后续会有很多复杂的内容,把 pre 保护了,就能保证它的原汁原味,因为 pre 本身就是代码,不能动。
    第三步,和 pre 一样的 code ,为什么先 pre 再 code 呢?因为这两样东西有这样的包含关系,一般 pre 里可以有 code ,但 code 却没有 pre ,所以在考虑这样的逻辑后,决定这样储存。
    第四步,就是在没有 pre 和 code 的干扰下,放心删除标签中其他没有用的属性,并将 a 和 img 的标签内容进行 “保护” ,以方便一会儿恢复。
    第五步,就是替换一些简单的标签,什么标题啊,斜体啊,横线啊等等(还有将一些乱七八糟的标签直接删除).....最后依次处理表格和列表。
    第六步,按照一定的规范,依次将上面 “保护” 的内容,进行恢复。
    第七步,将最头部的空行删去。(我记得中间也曾检查多余的空行删去,不知道为什么没有了),然后转换完毕,将结果返回。
    源码如下:
    1. /**
    2. * 把 html 内容转化为 markdown 格式 V1.0
    3. *
    4. * @author kohunglee
    5. * @param {string} htmlData 转换前的 html
    6. * @return {string} 转化后的 markdown 源码
    7. */
    8. function html2md(htmlData){
    9.     codeContent     = new Array  // code标签数据
    10.     preContent      = new Array  // pre标签数据
    11.     tableContent    = new Array  // table标签数据
    12.     olContent       = new Array  // ol标签数据
    13.     imgContent      = new Array  // img标签数据
    14.     aContent        = new Array  // a标签数据
    15.     let pureHtml    = htmlData

    16.     // 源代码
    17.     console.log("转换前的源码:" + pureHtml)

    18.     // 函数:删去html标签
    19.     function clearHtmlTag(sourceData = ''){  
    20.         return sourceData.replace(/\<[\s\S]*?\>/g,'')
    21.     }

    22.     // 复原ol标签
    23.     function olRecover(olData = ''){  
    24.         let result = olData
    25.         let num = olData.match(/\<li\>/ig).length
    26.         for(let i = 1; i <= num; i++){
    27.             let line = '[~wrap]'
    28.             if(i == 1) line = '[~wrap][~wrap]'
    29.             result = result.replace(/\<li\>/i, line + i + '. ')
    30.         }
    31.         result = result.replace(/\<\/li\>/, '')
    32.         return result
    33.     }

    34.     // 函数:复原img标签
    35.     function imgRecover(imgHtml = ''){  
    36.         let imgSrc,imgTit,imgAlt,result
    37.         imgSrc     = imgHtml.match(/(?<=src=['"])[\s\S]*?(?=['"])/i)
    38.         imgTit     = imgHtml.match(/(?<=title=['"])[\s\S]*?(?=['"])/i)
    39.         imgAlt     = imgHtml.match(/(?<=alt=['"])[\s\S]*?(?=['"])/i)

    40.         imgTit = (imgTit != null) ? ` "${imgTit}"` : ' '
    41.         imgAlt = (imgAlt != 'null') ? imgAlt : " "
    42.         result = `![${imgAlt}](${imgSrc}${imgTit})`
    43.         return result
    44.     }

    45.     // 函数:复原a标签
    46.     function aRecover(aData = ''){  
    47.         let aHref = '' + aData.match(/(?<=href=['"])[\s\S]*?(?=['"])/i)
    48.         let aTit  = '' + aData.match(/(?<=title=['"])[\s\S]*?(?=['"])/i)
    49.         let aText = '' + aData.match(/(?<=\<a\s*[^\>]*?\>)[\s\S]*?(?=<\/a>)/i)

    50.         let aImg = aData.match(/<img\s*[^\>]*?\>[^]*?(<\/img>)?/i)
    51.         let aImgSrc,aImgTit,aImgAlt

    52.         aTit = (aTit != 'null') ? ` "${aTit}"` : ' '
    53.         aText = clearHtmlTag(aText)
    54.         let result = `[${aText}](${aHref}${aTit})`
    55.         
    56.         if(aImg != null){  // 函数:如果发现图片,则更换为图片显示模式
    57.             aImgSrc     = aImg[0].match(/(?<=src=['"])[\s\S]*?(?=['"])/i)
    58.             aImgTit     = aImg[0].match(/(?<=title=['"])[\s\S]*?(?=['"])/i)
    59.             aImgAlt     = aImg[0].match(/(?<=alt=['"])[\s\S]*?(?=['"])/i)

    60.             aImgTit = (aImgTit != null) ? ` "${aImgTit}"` : ' '
    61.             aImgAlt = (aImgAlt != 'null') ? aImgAlt : " "
    62.             result = `[![${aImgAlt}](${aImgSrc}${aImgTit})](${aHref}${aTit})`
    63.         }
    64.         return result
    65.     }

    66.     // 函数:复原table标签
    67.     function tableRecover(tableData = null){  
    68.         if(tableData[0] == null){  // 如果不存在 th 标签,则默认表格为一层
    69.             let result = ''
    70.             let colNum = tableData[1].length

    71.             for(let i = 0; i < colNum; i++){
    72.             result += `|${clearHtmlTag(tableData[1][i])}`
    73.             }
    74.             result += `|[~wrap]`
    75.             for(let j = 0; j < colNum; j++){
    76.                 result += `| :------------: `
    77.             }
    78.             result += `|[~wrap]`
    79.             return result
    80.         }
    81.         let colNum = tableData[0].length  // 如果存在 th 标签,则按 th 的格数来构建整个表格
    82.         let result = ''

    83.         for(let i = 0; i < colNum; i++){
    84.             result += `|${clearHtmlTag(tableData[0][i])}`
    85.         }
    86.         result += `|[~wrap]`
    87.         for(let j = 0; j < colNum; j++){
    88.             result += `| :------------: `
    89.         }
    90.         result += `|[~wrap]`
    91.         for(let k = 0; k < tableData[1].length;){
    92.             for(let z = 0; z < colNum; z++,k++){
    93.                 result += `|${clearHtmlTag(tableData[1][k])}`
    94.             }
    95.             result += `|[~wrap]`
    96.         }
    97.         return result+`[~wrap]`
    98.     }
    99.     // 去掉样式和脚本极其内容
    100.     pureHtml = pureHtml.replace(/<style\s*[^\>]*?\>[^]*?<\/style>/ig,'').replace(/<script\s*[^\>]*?\>[^]*?<\/script>/ig,'')

    101.     // 储存pre的内容,并替换<pre>中的内容
    102.     preContent = pureHtml.match(/<pre\s*[^\>]*?\>[^]*?<\/pre>/ig)
    103.     pureHtml = pureHtml.replace(/(?<=\<pre\s*[^\>]*?\>)[\s\S]*?(?=<\/pre>)/ig,'`#preContent#`')

    104.     // 储存code的内容,并替换<code>中的内容
    105.     codeContent = pureHtml.match(/(?<=\<code\s*[^\>]*?\>)[\s\S]*?(?=<\/code>)/ig)
    106.     pureHtml = pureHtml.replace(/(?<=\<code\s*[^\>]*?\>)[\s\S]*?(?=<\/code>)/ig,'`#codeContent#`')

    107.     // 储存a的内容,并替换<a>中的内容
    108.     aContent = pureHtml.match(/<a\s*[^\>]*?\>[^]*?<\/a>/ig)
    109.     pureHtml = pureHtml.replace(/<a\s*[^\>]*?\>[^]*?<\/a>/ig,'`#aContent#`')

    110.     // 储存img的内容,并替换<img>中的内容
    111.     imgContent = pureHtml.match(/<img\s*[^\>]*?\>[^]*?(<\/img>)?/ig)
    112.     pureHtml = pureHtml.replace(/<img\s*[^\>]*?\>[^]*?(<\/img>)?/ig,'`#imgContent#`')

    113.     // 获取纯净(无属性)的 html
    114.     pureHtml = pureHtml.replace(/(?<=\<[a-zA-Z0-9]*)\s.*?(?=\>)/g,'')  

    115.     // 标题:标获取<h1><h2>...数据,并替换
    116.     pureHtml = pureHtml.replace(/<h1>/ig,'[~wrap]# ').replace(/<\/h1>/ig,'[~wrap][~wrap]')
    117.                         .replace(/<h2>/ig,'[~wrap]## ').replace(/<\/h2>/ig,'[~wrap][~wrap]')
    118.                         .replace(/<h3>/ig,'[~wrap]### ').replace(/<\/h3>/ig,'[~wrap][~wrap]')
    119.                         .replace(/<h4>/ig,'[~wrap]#### ').replace(/<\/h4>/ig,'[~wrap][~wrap]')
    120.                         .replace(/<h5>/ig,'[~wrap]##### ').replace(/<\/h5>/ig,'[~wrap][~wrap]')
    121.                         .replace(/<h6>/ig,'[~wrap]###### ').replace(/<\/h6>/ig,'[~wrap][~wrap]')

    122.     // 段落:处理一些常用的结构标签
    123.     pureHtml = pureHtml.replace(/(<br>)/ig,'[~wrap]').replace(/(<\/p>)|(<br\/>)|(<\/div>)/ig,'[~wrap][~wrap]')
    124.                        .replace(/(<meta>)|(<span>)|(<p>)|(<div>)/ig,'').replace(/<\/span>/ig,'')

    125.     // 粗体:替换<b><strong>
    126.     pureHtml = pureHtml.replace(/(<b>)|(<strong>)/ig,'**').replace(/(<\/b>)|(<\/strong>)/ig,'**')

    127.     // 斜体:替换<i><em><abbr><dfn><cite><address>
    128.     pureHtml = pureHtml.replace(/(<i>)|(<em>)|(<abbr>)|(<dfn>)|(<cite>)|(<address>)/ig,'*').replace(/(<\/i>)|(<\/em>)|(<\/abbr>)|(<\/dfn>)|(<\/cite>)|(<\/address>)/ig,'*')

    129.     // 删除线:替换<del>
    130.     pureHtml = pureHtml.replace(/\<del\>/ig,'~~').replace(/\<\/del\>/ig,'~~')

    131.     // 引用:替换<blockquote>
    132.     pureHtml = pureHtml.replace(/\<blockquote\>/ig,'[~wrap][~wrap]> ').replace(/\<\/blockquote\>/ig,'[~wrap][~wrap]')

    133.     // 水平线:替换<hr>
    134.     pureHtml = pureHtml.replace(/\<hr\>/ig,'[~wrap][~wrap]------[~wrap][~wrap]')

    135.     // 表格 <table>,得到数据,删除标签,然后逐层分析储存,最终根据结果生成
    136.     tableContent = pureHtml.match(/(?<=\<table\s*[^\>]*?\>)[\s\S]*?(?=<\/table>)/ig)
    137.     pureHtml = pureHtml.replace(/<table\s*[^\>]*?\>[^]*?<\/table>/ig,'`#tableContent#`')
    138.     if(tableContent !== null){  // 分析储存
    139.         tbodyContent = new Array
    140.         for(let i = 0; i < tableContent.length; i++){
    141.             tbodyContent[i] = new Array  // tbodyContent[i]的第一个数据是thead数据,第二个是tbody的数据
    142.             tbodyContent[i].push(tableContent[i].match(/(?<=\<th>)[\s\S]*?(?=<\/th?>)/ig))
    143.             tbodyContent[i].push(tableContent[i].match(/(?<=\<td>)[\s\S]*?(?=<\/td?>)/ig))
    144.         }
    145.     }
    146.     if(typeof tbodyContent !== "undefined"){  // 替换
    147.         for(let i = 0; i < tbodyContent.length; i++){
    148.             let tableText = tableRecover(tbodyContent[i])
    149.             pureHtml = pureHtml.replace(/\`\#tableContent\#\`/i,tableText)
    150.         }
    151.     }
    152.    
    153.     // 有序列表<ol>的<li>,储存ol的内容,并循环恢复ol中的内容
    154.     olContent = pureHtml.match(/(?<=\<ol\s*[^\>]*?\>)[\s\S]*?(?=<\/ol>)/ig)
    155.     pureHtml = pureHtml.replace(/(?<=\<ol\s*[^\>]*?\>)[\s\S]*?(?=<\/ol>)/ig,'`#olContent#`')
    156.     if(olContent !== null){
    157.         for(let k = 0; k < olContent.length; k++){
    158.             let olText = olRecover(olContent[k])
    159.             pureHtml = pureHtml.replace(/\`\#olContent\#\`/i,clearHtmlTag(olText))
    160.         }
    161.     }

    162.     // 无序列表<ul>的<li>,以及<dd>,直接替换
    163.     pureHtml = pureHtml.replace(/(<li>)|(<dd>)/ig,'[~wrap] - ').replace(/(<\/li>)|(<\/dd>)/ig,'[~wrap][~wrap]')

    164.     // 处理完列表后,将 <lu>、<\lu>、<ol>、<\ol> 处理
    165.     pureHtml = pureHtml.replace(/(<ul>)|(<ol>)/ig,'').replace(/(<\/ul>)|(<\/ol>)/ig,'[~wrap][~wrap]')

    166.     // 先恢复 img ,再恢复 a
    167.     if(imgContent !== null){
    168.         for(let i = 0; i < imgContent.length; i++){
    169.             let imgText = imgRecover(imgContent[i])
    170.             pureHtml = pureHtml.replace(/\`\#imgContent\#\`/i,imgText)
    171.         }
    172.     }

    173.     // 恢复 a
    174.     if(aContent !== null){
    175.         for(let k = 0; k < aContent.length; k++){
    176.             let aText = aRecover(aContent[k])
    177.             pureHtml = pureHtml.replace(/\`\#aContent\#\`/i,aText)
    178.         }
    179.     }

    180.     // 换行处理,1.替换 [~wrap] 为 ‘\n'   2.首行换行删去。   3.将其他过长的换行删去。
    181.     pureHtml = pureHtml.replace(/\\[\~wrap\\]/ig,'\n')
    182.                        .replace(/\n{3,}/g,'\n\n')

    183.     // 代码 <code> ,根据上面的数组恢复code,然后将code替换
    184.     if(codeContent !== null){
    185.         for(let i = 0; i < codeContent.length; i++){
    186.             pureHtml = pureHtml.replace(/\`\#codeContent\#\`/i,clearHtmlTag(codeContent[i]))
    187.         }
    188.     }
    189.     pureHtml = pureHtml.replace(/\<code\>/ig,' ` ').replace(/\<\/code\>/ig,' ` ')

    190.     // 代码 <pre> ,恢复pre,然后将pre替换
    191.     if(preContent !== null){
    192.         for(let k = 0; k < preContent.length; k++){
    193.             let preLanguage = preContent[k].match(/(?<=language-).*?(?=[\s'"])/i)
    194.             let preText = clearHtmlTag(preContent[k])
    195.             preText = preText.replace(/^1\n2\n(\d+\n)*/,'')  // 去掉行数

    196.             preLanguage = (preLanguage != null && preLanguage[0] != 'undefined') ? preLanguage[0] + '\n' : '\n'
    197.             pureHtml = pureHtml.replace(/\`\#preContent\#\`/i,preLanguage + preText)
    198.         }
    199.     }
    200.     pureHtml = pureHtml.replace(/\<pre\>/ig,'```').replace(/\<\/pre\>/ig,'\n```\n')

    201.     // 删去其余的html标签,还原预文本代码中的 '<' 和 '>'
    202.     pureHtml = clearHtmlTag(pureHtml)
    203.     pureHtml = pureHtml.replace(/\&lt\;/ig,'<').replace(/\&gt\;/ig,'>')

    204.     // 删去头部的空行
    205.     pureHtml = pureHtml.replace(/^\n{1,}/i,'')

    206.     return pureHtml
    207. }
    复制代码
    到此这篇关于原生JS实现HTML转Markdown功能的文章就介绍到这了,更多相关JS HTML转Markdown内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    来源:https://www.jb51.net/javascript/3398492hi.htm
    免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    ×

    最新评论

    QQ Archiver 手机版 小黑屋 福建二哥 ( 闽ICP备2022004717号|闽公网安备35052402000345号 )

    Powered by Discuz! X3.5 © 2001-2023

    快速回复 返回顶部 返回列表