代码语言:javascript复制
import COMMONMARK_RULES from './commonmark-rules'
import Rules from './rules'
import { extend, trimLeadingNewlines, trimTrailingNewlines } from './utilities'
import RootNode from './root-node'
import Node from './node'
var reduce = Array.prototype.reduce
// 定义替换模式表
// 第一个元素是模式串,第二个元射弩是替换字符串
var escapes = [
[/\/g, '\\'],
[/*/g, '\*'],
[/^-/g, '\-'],
[/^ /g, '\ '],
[/^(= )/g, '\$1'],
[/^(#{1,6}) /g, '\$1 '],
[/`/g, '\`'],
[/^~~~/g, '\~~~'],
[/[/g, '\['],
[/]/g, '\]'],
[/^>/g, '\>'],
[/_/g, '\_'],
[/^(d ). /g, '$1\. ']
]
export default function TurndownService (options) {
// 如果不是用 new 调用的强制用 new 调用
if (!(this instanceof TurndownService)) return new TurndownService(options)
// 定义默认配置
var defaults = {
rules: COMMONMARK_RULES, // 反编译各个元素的规则
headingStyle: 'setext', // 标题格式 atx || setext
hr: '* * *', // 行分隔符格式
bulletListMarker: '*', // 列表前缀
codeBlockStyle: 'indented', // 代码块格式
fence: '```', // 代码快分隔符
emDelimiter: '_', // 斜体分隔符
strongDelimiter: '**', // 粗体分隔符
linkStyle: 'inlined', // 链接格式
linkReferenceStyle: 'full', // 引用链接的格式
br: ' ', // 换行后缀
preformattedCode: false, // 是否格式化代码
// 移除规则的替换方法,移除元素内容
blankReplacement: function (content, node) {
return node.isBlock ? 'nn' : ''
},
// 保留规则的替换方法,将元素的 HTML 保持不变
keepReplacement: function (content, node) {
return node.isBlock ? 'nn' node.outerHTML 'nn' : node.outerHTML
},
// 默认的替换方法,将元素的标签去除,内容保持不变
defaultReplacement: function (content, node) {
return node.isBlock ? 'nn' content 'nn' : content
}
}
// 将用户配置和默认配置组合
// 优先采用用户配置中的值
this.options = extend({}, defaults, options)
// 创建规则集
this.rules = new Rules(this.options)
}
TurndownService.prototype = {
/**
* The entry point for converting a string or DOM node to Markdown
* @public
* @param {String|HTMLElement} input The string or DOM node to convert
* @returns A Markdown representation of the input
* @type String
*/
turndown: function (input) {
// 判断是否是字符串或者元素
if (!canConvert(input)) {
throw new TypeError(
input ' is not a string, or an element/document/fragment node.'
)
}
// 空串处理
if (input === '') return ''
// 将输入包含在单个根节点中,然后处理每个子元素
var output = process.call(this, new RootNode(input, this.options))
// 后处理每个子元素
return postProcess.call(this, output)
},
/**
* Add one or more plugins
* @public
* @param {Function|Array} plugin The plugin or array of plugins to add
* @returns The Turndown instance for chaining
* @type Object
*/
use: function (plugin) { if (Array.isArray(plugin)) {
// 如果插件是数组
// 对其每个元素地柜调用此函数
for (var i = 0; i < plugin.length; i ) this.use(plugin[i])
} else if (typeof plugin === 'function') {
// 如果是函数,就直接调用还函数
plugin(this)
} else {
// 否则抛异常
throw new TypeError('plugin must be a Function or an Array of Functions')
}
return this
},
/**
* Adds a rule
* @public
* @param {String} key The unique key of the rule
* @param {Object} rule The rule
* @returns The Turndown instance for chaining
* @type Object
*/
addRule: function (key, rule) {
// 向规则集添加规则
this.rules.add(key, rule)
return this
},
/**
* Keep a node (as HTML) that matches the filter
* @public
* @param {String|Array|Function} filter The unique key of the rule
* @returns The Turndown instance for chaining
* @type Object
*/
keep: function (filter) {
// 向规则集添加保留规则
this.rules.keep(filter)
return this
},
/**
* Remove a node that matches the filter
* @public
* @param {String|Array|Function} filter The unique key of the rule
* @returns The Turndown instance for chaining
* @type Object
*/
remove: function (filter) {
// 向规则集添加移除规则
this.rules.remove(filter)
return this
},
/**
* Escapes Markdown syntax
* @public
* @param {String} string The string to escape
* @returns A string with Markdown syntax escaped
* @type String
*/
escape: function (string) {
// 遍历表中的每一行,将字符串中的第一项替换为第二项
return escapes.reduce(function (accumulator, escape) {
return accumulator.replace(escape[0], escape[1])
}, string)
}
}
/**
* Reduces a DOM node down to its Markdown string equivalent
* @private
* @param {HTMLElement} parentNode The node to convert
* @returns A Markdown representation of the node
* @type String
*/
// 获取节点的内部 MD,等于所有子节点外部MD的连接
function process (parentNode) {
var self = this
// 遍历每个子节点,解析它的 MD 内容,之后合并
return reduce.call(parentNode.childNodes, function (output, node) {
// 给节点添加一些属性
node = new Node(node, self.options)
var replacement = ''
if (node.nodeType === 3) {
// 如果是个文本,判断它是不是代码块的一部分,不是的话对其转义。
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue)
} else if (node.nodeType === 1) {
// 如果是元素,那么获取其外部MD
replacement = replacementForNode.call(self, node)
}
// 连接每个部分
return join(output, replacement)
}, '')
}
/**
* Appends strings as each rule requires and trims the output
* @private
* @param {String} output The conversion output
* @returns A trimmed version of the ouput
* @type String
*/
// 后处理,处理规则的附加部分
function postProcess (output) {
var self = this
// 对于每个规则,检查是否有 append 方法
// 如果存在,就调用它获取文本,并追加到 output 上
this.rules.forEach(function (rule) {
if (typeof rule.append === 'function') {
output = join(output, rule.append(self.options))
}
})
// 将首尾空白移除
return output.replace(/^[trn] /, '').replace(/[trns] $/, '')
}
/**
* Converts an element node to its Markdown equivalent
* @private
* @param {HTMLElement} node The node to convert
* @returns A Markdown representation of the node
* @type String
*/
// 获取节点的外部 MD
function replacementForNode (node) {
// 获取适用于该节点的规则
var rule = this.rules.forNode(node)
// 获取该节点的 MD 内容
var content = process.call(this, node)
// 为该节点加上是适当的前导和尾随空白
var whitespace = node.flankingWhitespace
if (whitespace.leading || whitespace.trailing) content = content.trim()
// 使用规则的替换函数生成整个节点的 MD,然后拼接空白并返回
return (
whitespace.leading
rule.replacement(content, node, this.options)
whitespace.trailing
)
}
/**
* Joins replacement to the current output with appropriate number of new lines
* @private
* @param {String} output The current conversion output
* @param {String} replacement The string to append to the output
* @returns Joined output
* @type String
*/
function join (output, replacement) {
// 移除第一个字符串的尾随换行
var s1 = trimTrailingNewlines(output)
// 移除第二个字符串的前导换行
var s2 = trimLeadingNewlines(replacement)
// 计算尾随和前导换行长度,取最大值为 nls
var nls = Math.max(output.length - s1.length, replacement.length - s2.length)
// 填充等于 nls 个换行
var separator = 'nn'.substring(0, nls)
// 拼接字符串并返回
return s1 separator s2
}
/**
* Determines whether an input can be converted
* @private
* @param {String|HTMLElement} input Describe this parameter
* @returns Describe what it returns
* @type String|Object|Array|Boolean|Number
*/
function canConvert (input) {
// 如果输入是字符串或者 HTML 元素,或者其他特定类型节点则返回真
return (
input != null && (
typeof input === 'string' ||
(input.nodeType && (
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11
))
)
)
}