之前因为一些需要,需要转换部分 HTML 标签成 markdown 格式,但是不知不觉就完善到一个相对完整的函数。
然后我就封装成了一个文件放在了 github ,也简单做了两个示例网页。
- HTML 转换 — https://kohunglee.github.io/html2md/example/conversion.html
- 直接就粘贴成 markdown 格式 — https://kohunglee.github.io/html2md/example/Paste_and_convert.html
代码地址在 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 = `` 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 = `[](${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(/\<\;/ig,'<').replace(/\>\;/ig,'>') // 删去头部的空行 pureHtml = pureHtml.replace(/^\n{1,}/i,'') return pureHtml }
到此这篇关于原生JS实现HTML转Markdown功能的文章就介绍到这了,更多相关JS HTML转Markdown内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
来源链接:https://www.jb51.net/javascript/3398492hi.htm
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
暂无评论内容