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

然后我就封装成了一个文件放在了 github ( https://github.com/kohunglee/html2md )- 希望能给个 star 哈哈,感激不尽,无比感谢!

也简单做了两个示例网页。

  1. HTML 转换 -- https://ccgxk.com/html2md/example/conversion.html
  2. 直接就粘贴成 markdown 格式 -- https://ccgxk.com/html2md/example/Paste_and_convert.html

代码很简单,用的也是原生 js ,其中包含了大量简单的正则,也没有使用更多的函数进行优化结构,但这个函数确实挺好用的,后来还在它的基础上做了个 emlog 的插件,希望对一些人有用吧。

插件地址是 https://www.emlog.net/plugin/detail/445

那么我也懒得对它进一步的优化了,这里就简单将源码贴出来吧,然后再讲一下我是怎么实现这个功能的,希望有的人会对它感兴趣。

源码一共二百多行,但我知道确实可以写的更简单。

``

点击查看代码
/**
* 把 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
}

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

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

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

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

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

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

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

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

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

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

原生 JS 实现 HTML 转 Markdown ,(html2md.js 或 html2markdown.js)的更多相关文章

  1. JS调用OC方法并传值,OC调用JS方法并传值////////////////////////zz

     iOS开发-基于原生JS与OC方法互相调用并传值(附HTML代码)     最近项目里面有有个商品活动界面,要与web端传值,将用户在网页点击的商品id 传给客户端,也就是js交互,其实再说明白一点 ...

  2. Ext JS学习第八天 Ext基础之 认识Ext.js 和Ext-more.js

    此文来记录学习笔记: •认识Ext.js 和Ext-more.js •首先,我们要知道这2个js文件的作用,这俩个文件包含了Ext的一些基础定义.基本的属性和方法,我们要重点学习和深入底层代码进行研究 ...

  3. App.js和App.css(用于移动应用的js和css)

    App.js和App.css(用于移动应用的js和css) 一.App.js和App.css(用于移动应用的js和css) App.js 是一个轻量级的 JavaScript UI 库,用来创建移动的 ...

  4. JS组件系列——开源免费图表组件:Chart.js

    前言:最近被开源免费得有点上火了,各种组件首先想到的就是是开源否.是否免费.是否和bootstrap风格一致.想着以后做报表肯定要用到图表组件的,于是在Bootstrap中文网上面找到了Chart.j ...

  5. js 中使用el表达式 关键总结:在js中使用el表达式一定要使用双引号

    js 中使用el表达式 关键总结:在js中使用el表达式一定要加双引号 js控制中用到了el表达式,最开始源码如下: var selected = ${requestScope.xxxxForm.re ...

  6. WinForm程序执行JS代码的多种方法以及使用WebBrowser与JS交互

    方法一 使用微软官方组件Interop.MSScriptControl 1.msscript.ocx下载的地址   http://www.microsoft.com/downloads/details ...

  7. Numeral.js 是一个用于格式化和数字四则运算的js 库

    1.Numeral.js 是一个用于格式化和数字四则运算的js 库. 2.支持多种语言,包含中文在内的17种语言. 在浏览器中引用js文件: <script src="numeral. ...

  8. c#调用js,以及js调用C#里的函数, c#自己生成js代码,实现对web的控制

    using mshtml;using System;using System.Collections.Generic;using System.Linq;using System.Security.P ...

  9. JS 事件循环机制 - 任务队列、web API、JS主线程的相互协同

    一.JS单线程.异步.同步概念 从上一篇说明vue nextTick的文章中,多次出现“事件循环”这个名词,简单说明了事件循环的步骤,以便理解nextTick的运行时机,这篇文章将更为详细的分析下事件 ...

  10. sublime格式化js、css、html的通用插件-html js css pretty

    sublime格式化js.css.html的通用插件-html js css pretty: 这个插件可以格式化基本上所有js html css文件,包括写在html中的js代码 ,可以在packag ...

随机推荐

  1. 微信小游戏直播在Android端的跨进程渲染推流实践

    本文由微信开发团队工程师"virwu"分享. 1.引言 近期,微信小游戏支持了视频号一键开播,将微信升级到最新版本,打开腾讯系小游戏(如跳一跳.欢乐斗地主等),在右上角菜单就可以看 ...

  2. Kubernetes系列(一) - kubernetes入门基本概念

    目录 1. 基本概念 1.1 什么是 Kubernetes 集群 1.2 Kubernetes集群资源组成: 1.3 无状态和有状态的区别 1.3.1 无状态服务 1.3.2 有状态服务 2. Kub ...

  3. C#+ WPF 实现蓝牙转WIFI计步上位机

    前言 一个WIFI上位机,接收底层MPU6050数据,途中转蓝牙从机透传,到蓝牙主机直连WIFI,PC端UDP通信,实现三轴加速度数据传送和计步功能. 项目介绍 本项目基于.NET平台,使用WPF开发 ...

  4. java代码之美(2)

    guava 复写Object常用方法 Guava 是一个 Google 的基于java1.6的类库集合的扩展项目,这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方 ...

  5. Superset 下钻与交叉筛选

    以下内容来自外网:https://github.com/apache/superset/issues/6774 具体查看外网,我目前还没有琢磨,如果你有了思路请再下方留言 下面这个网址也有个交叉筛选: ...

  6. Django设置跨域请求解决方案

    Django设置跨域请求解决方案 在现代Web开发中,跨域资源共享(CORS,Cross-Origin Resource Sharing)是一个常见的需求.尤其是在前后端分离的开发模式下,Django ...

  7. Flink程序异常--CommunicationsException: The last packet successfully received from the server was

    一.异常截图 com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully receive ...

  8. pycharm上传github问题:rejected

    我从pycharm上传项目时,遇到的问题: 以下是一些解决思路: 这个错误提示表明,你在尝试将本地代码推送到远程仓库时,远程仓库中已经包含了你本地尚未获取的更改.换句话说,远程仓库的代码比你的本地代码 ...

  9. 面试官最想听到的Vue和React区别

    前言 欧阳最近找工作面试时总是被问到两个问题:Vue和React的区别和从编译原理的角度来聊聊Vue的template和React的jsx.面试官问这些问题一般是想了解你对这两个框架的理解,所以这是一 ...

  10. 克鲁斯焊机GL 270引弧困难维修

    克鲁斯焊机维修: 对于客户而言,其受益之处在于所有的机械手系统部件,从机械手控制.工件.定位器.传感器到电源和焊枪,都是由一家供应商开发和制造的.而诸如熔化极惰性气体/活性气体保护双丝焊接技术.等离子 ...