首页 > 编程语言 > Turndown 源码分析:四、`turndown.js`

Turndown 源码分析:四、`turndown.js`

时间:2023-06-19 14:46:52浏览次数:47  
标签:node Turndown return String function js 源码 input output

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 ? '\n\n' : ''
    },
    // 保留规则的替换方法,将元素的 HTML 保持不变
    keepReplacement: function (content, node) {
      return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
    },
    // 默认的替换方法,将元素的标签去除,内容保持不变
    defaultReplacement: function (content, node) {
      return node.isBlock ? '\n\n' + content + '\n\n' : 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(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
}

/**
 * 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 = '\n\n'.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
      ))
    )
  )
}

标签:node,Turndown,return,String,function,js,源码,input,output
From: https://www.cnblogs.com/apachecn/p/17491100.html

相关文章

  • vite+vue3项目中使用 lottie 动画,如何在 template 中直接使用本地 json 文件路径
    安装lottie-webyarnaddlottie-web封装 lottie组件<template><divref="animation":style="{width,height}"></div></template><script>import{defineComponent,ref,onMounted}from'vue'......
  • nodejs 伪全局变量模块
    使用这个文件可以实现不同文件中读写变量,适合当做共享变量文件名:globals.tsletglobals:any={myGlobal:{value:'canbeanytype:String,Array,Object,...'},aReadonlyGlobal:{value:'thisvalueisreadonly',protected:......
  • app直播源代码,JS生成随机数,生成指定位数的随机数
    app直播源代码,JS生成随机数,生成指定位数的随机数<html><script> //获取指定位数的随机数 functiongetRandom(num){  letrandom=Math.floor((Math.random()+Math.floor(Math.random()*9+1))*Math.pow(10,num-1)); } //调用随机数函数生成10位数的随机......
  • auto.js自动化手机脚本初始配置
    软件选择:auto.js8.0pro版本(对比4.0版本有阉割,微信支付宝不能点)有两种模式:客户端模式服务器模式auto.js4.0版本有一种模式:客户端模式设备和电脑连接:手机:服务器模式:手机和电脑在同一局域网下相互连接。(手机电脑同wifi远程连接,稳定)客户端模式:电脑连接以太网(不是虚拟机的......
  • Android中高级开发进阶必备资料(附:PDF+视频+源码笔记)
    前言Android开发学习过程中要掌握好基础知识,特别是java语言的应用,然后逐步提升开发者在学习过程中遇到的一些细致化的问题,把一些难点进行解决,在开发过程中把容易出现的一些难点进行合理化控制,避免在程序生成产品后出现问题,从而导致崩溃,这是非常重要的一点。架构师筑基必备技能作为......
  • JSP连接数据库大全
    JSP连接数据库大全一、jsp连接Oracle8/8i/9i数据库(用thin模式)testoracle.jsp如下:<%@pagecontentType="text/html;charset=gb2312"%><%@pageimport="java.sql.*"%><html><body><%Class.forName(......
  • Three.js教程:动画渲染循环
    推荐:将NSDT场景编辑器加入你的3D工具链其他系列工具:NSDT简石数字孪生动画渲染循环threejs可以借助HTML5的API请求动画帧window.requestAnimationFrame实现动画渲染。请求动画帧window.requestAnimationFrame//requestAnimationFrame实现周期性循环执行//requestAnimationF......
  • 关于ASP.NET.CORE中的Failed to read parameter "string param" from the request bod
    先上报错信息Microsoft.AspNetCore.Http.BadHttpRequestException:Failedtoreadparameter"stringparam"fromtherequestbodyasJSON.--->System.Text.Json.JsonException:'s'isaninvalidstartofavalue.Path:$|LineNumber:0|By......
  • jsp WebUploader 分块上传
    ​ 前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会忍受,当文件上传到一半中断后,继续上传却只能重头开始上传,这种让人不爽的体验。那有没有......
  • 如何有效阅读源码?最新Android开发源码精编解析,优秀程序员必备
    大多数人阅读源码是为了应对面试中可能会提到的相关问题,提高面试的成功率,因此选择源码相关的书籍和视频来看是速成的最好方法。但对于想真正提高编码水平,让自己的事业更上一层楼的开发者而言,只有下功夫、花时间,才能有所突破。不过大家也清楚,阅读源码是比较困难的,尤其是对于项目背景......