首页 > 编程语言 >Turndown 源码分析:二、规则`commonmark-ruiles.js` REV1

Turndown 源码分析:二、规则`commonmark-ruiles.js` REV1

时间:2023-06-19 16:11:30浏览次数:49  
标签:node Turndown return function commonmark content 源码 var options

import { repeat } from './utilities'

var rules = {}

// 段落
rules.paragraph = {
  filter: 'p',

  replacement: function (content) {
    // 前后加两个换行
    return '\n\n' + content + '\n\n'
  }
}

// 换行
rules.lineBreak = {
  filter: 'br',

  replacement: function (content, node, options) {
    // 换行的规则在各个编辑器中是不统一的
    // GH 实现只需要一个换行就够,但经典实现需要两个空格加一个换行
    // options.br 用于配置换行符之前应该添加的字符
    return options.br + '\n'
  }
}


// 标题
rules.heading = {
  filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

  replacement: function (content, node, options) {
    // 确定标题级别(节点名称第二个字母)
    var hLevel = Number(node.nodeName.charAt(1))

    if (options.headingStyle === 'setext' && hLevel < 3) {
      // 如果是 setext 风格,标题文本下加相同长度的等号或者中划线
      var underline = repeat((hLevel === 1 ? '=' : '-'), content.length)
      return (
        '\n\n' + content + '\n' + underline + '\n\n'
      )
    } else {
      // 如果是 atx 风格,标题前面加上级别等量的井号
      return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
    }
    // 前后加上两个换行
  }
}

// 引用块
rules.blockquote = {
  filter: 'blockquote',

  replacement: function (content) {
    // 替换掉前导和尾随换行
    content = content.replace(/^\n+|\n+$/g, '')
    // 每行开头加上前缀
    content = content.replace(/^/gm, '> ')
    // 前后加上两个换行
    return '\n\n' + content + '\n\n'
  }
}

// 列表
rules.list = {
  filter: ['ul', 'ol'],

  replacement: function (content, node) {
    // 检查元素是顶级列表还是子列表
    var parent = node.parentNode
    if (parent.nodeName === 'LI' && parent.lastElementChild === node) {
      // 如果是子列表,前面加一个换行,后面不加
      return '\n' + content
    } else {
      // 如果是顶级列表,前后加两个换行
      return '\n\n' + content + '\n\n'
    }
  }
}

// 列表项
rules.listItem = {
  filter: 'li',

  replacement: function (content, node, options) {
    content = content
      .replace(/^\n+/, '') // 移除前导换行
      .replace(/\n+$/, '\n') // 将尾随换行减少为一个
      .replace(/\n/gm, '\n    ') // 曾伽缩进
    // 无序列表的列表项前缀
    var prefix = options.bulletListMarker + '   '
    var parent = node.parentNode
    if (parent.nodeName === 'OL') {
      // 如果是有序列表,获取起始序号
      var start = parent.getAttribute('start')
      // 获取列表项的索引
      var index = Array.prototype.indexOf.call(parent.children, node)
      // 有序号则使用序号,否则使用索引
      prefix = (start ? Number(start) + index : index + 1) + '.  '
    }
    // 如果不是最后一个列表项,并且没有尾随换行,则添加一个
    return (
      prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '')
    )
  }
}

// 缩进式代码块
rules.indentedCodeBlock = {
  filter: function (node, options) {
    return (
      options.codeBlockStyle === 'indented' &&
      node.nodeName === 'PRE' &&
      node.firstChild &&
      node.firstChild.nodeName === 'CODE'
    )
  },

  replacement: function (content, node, options) {
    // 每一行加缩进,首尾加两个换行
    return (
      '\n\n    ' +
      node.firstChild.textContent.replace(/\n/g, '\n    ') +
      '\n\n'
    )
  }
}

// GH 风格代码块
rules.fencedCodeBlock = {
  filter: function (node, options) {
    return (
      options.codeBlockStyle === 'fenced' &&
      node.nodeName === 'PRE' &&
      node.firstChild &&
      node.firstChild.nodeName === 'CODE'
    )
  },

  replacement: function (content, node, options) {
    // 获取其 CODE 子元素的类名
    var className = node.firstChild.getAttribute('class') || ''
    // 从类名获取语言
    var language = (className.match(/language-(\S+)/) || [null, ''])[1]
    var code = node.firstChild.textContent

    // 确定分隔符的长度,是代码中出现的最大连续分隔字符数量加1
    var fenceChar = options.fence.charAt(0)
    var fenceSize = 3
    var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm')

    var match
    while ((match = fenceInCodeRegex.exec(code))) {
      if (match[0].length >= fenceSize) {
        fenceSize = match[0].length + 1
      }
    }

    var fence = repeat(fenceChar, fenceSize)
    // 拼装字符串并返回
    return (
      '\n\n' + fence + language + '\n' +
      code.replace(/\n$/, '') +
      '\n' + fence + '\n\n'
    )
  }
}

// 水平线
rules.horizontalRule = {
  filter: 'hr',

  replacement: function (content, node, options) {
   // 使用 options.hr 的样式,前后两个换行
    return '\n\n' + options.hr + '\n\n'
  }
}

// 内联连接
rules.inlineLink = {
  filter: function (node, options) {
    return (
      options.linkStyle === 'inlined' &&
      node.nodeName === 'A' &&
      node.getAttribute('href')
    )
  },

  replacement: function (content, node) {
    // 获取 URL 和标题
    var href = node.getAttribute('href')
    var title = cleanAttribute(node.getAttribute('title'))
    if (title) title = ' "' + title + '"'
    // 拼接到一起
    return '[' + content + '](' + href + title + ')'
  }
}

// 引用链接
rules.referenceLink = {
  filter: function (node, options) {
    return (
      options.linkStyle === 'referenced' &&
      node.nodeName === 'A' &&
      node.getAttribute('href')
    )
  },

  replacement: function (content, node, options) {
    // 获取 URL 和标题
    var href = node.getAttribute('href')
    var title = cleanAttribute(node.getAttribute('title'))
    if (title) title = ' "' + title + '"'
    var replacement
    var reference

    // 根据不同格式构造链接部分和引用部分
    switch (options.linkReferenceStyle) {
      case 'collapsed':
        replacement = '[' + content + '][]'
        reference = '[' + content + ']: ' + href + title
        break
      case 'shortcut':
        replacement = '[' + content + ']'
        reference = '[' + content + ']: ' + href + title
        break
      default:
        var id = this.references.length + 1
        replacement = '[' + content + '][' + id + ']'
        reference = '[' + id + ']: ' + href + title
    }

    // 将引用部分插入到引用列表中,稍后附加到文章末尾
    this.references.push(reference)
    // 返回链接部分
    return replacement
  },

  references: [],

  append: function (options) {
    var references = ''
    // 将引用列表按行连接,一行一个引用,然后清空引用列表
    if (this.references.length) {
      references = '\n\n' + this.references.join('\n') + '\n\n'
      this.references = [] // Reset references
    }
    return references
  }
}

// 斜体
rules.emphasis = {
  filter: ['em', 'i'],

  replacement: function (content, node, options) {
    // 如果内容为空或者空白, 返回空串
    if (!content.trim()) return ''
    // 返回分隔符包围的内容
    return options.emDelimiter + content + options.emDelimiter
  }
}

rules.strong = {
  filter: ['strong', 'b'],

  replacement: function (content, node, options) {
    // 如果内容为空或者空白, 返回空串
    if (!content.trim()) return ''
    // 返回分隔符包围的内容
    return options.strongDelimiter + content + options.strongDelimiter
  }
}

// 内联代码
rules.code = {
  filter: function (node) {
    var hasSiblings = node.previousSibling || node.nextSibling
    var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings

    return node.nodeName === 'CODE' && !isCodeBlock
  },

  replacement: function (content) {
    // 如果内容为空或者空白, 返回空串
    if (!content) return ''
    // 去掉所有换行符
    content = content.replace(/\r?\n|\r/g, ' ')

    // 如果以反引号开头或者结尾,需要加填充避免与分隔符连上
    var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : ''
    // 分隔符的长度是代码中出现的最大连续反引号数量加1
    var delimiter = '`'
    var matches = content.match(/`+/gm) || []
    while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'
    // 拼接到一起
    return delimiter + extraSpace + content + extraSpace + delimiter
  }
}

// 图象
rules.image = {
  filter: 'img',

  replacement: function (content, node) {
    // 获取标题、描述和链接并拼接起来
    // 链接不存在时返回空串
    var alt = cleanAttribute(node.getAttribute('alt'))
    var src = node.getAttribute('src') || ''
    var title = cleanAttribute(node.getAttribute('title'))
    var titlePart = title ? ' "' + title + '"' : ''
    return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
  }
}

function cleanAttribute (attribute) {
  // 将连续换行变成单个换行,将行首空格移除。
  return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
}

export default rules

标签:node,Turndown,return,function,commonmark,content,源码,var,options
From: https://www.cnblogs.com/apachecn/p/17491397.html

相关文章

  • 墙裂推荐,Android 开发百大框架源码精编解析
    为什么要读源码?源码也是目前大厂面试比较喜欢问的,研究过源码要从广度和深度去挖掘。为什么要进行源码分析。其中包括下面一些好处:学习Android源码有助于我们学习其中的设计模式、思想、架构。熟悉整个源码的架构,有助于我们更加正确地调用Android提供的SDK,写出高效正确的代码。学......
  • Turndown 源码分析:四、`turndown.js`
    importCOMMONMARK_RULESfrom'./commonmark-rules'importRulesfrom'./rules'import{extend,trimLeadingNewlines,trimTrailingNewlines}from'./utilities'importRootNodefrom'./root-node'importNodefrom'......
  • Android中高级开发进阶必备资料(附:PDF+视频+源码笔记)
    前言Android开发学习过程中要掌握好基础知识,特别是java语言的应用,然后逐步提升开发者在学习过程中遇到的一些细致化的问题,把一些难点进行解决,在开发过程中把容易出现的一些难点进行合理化控制,避免在程序生成产品后出现问题,从而导致崩溃,这是非常重要的一点。架构师筑基必备技能作为......
  • 如何有效阅读源码?最新Android开发源码精编解析,优秀程序员必备
    大多数人阅读源码是为了应对面试中可能会提到的相关问题,提高面试的成功率,因此选择源码相关的书籍和视频来看是速成的最好方法。但对于想真正提高编码水平,让自己的事业更上一层楼的开发者而言,只有下功夫、花时间,才能有所突破。不过大家也清楚,阅读源码是比较困难的,尤其是对于项目背景......
  • 我快被Framework源码烦死了
    前言这段时间,忙到没时间学新东西,都有点心有余而力不足,想着抽空补补课,于是重读了Framework源码。因为Framework源码太重要了,像掉帧监控、函数插装、慢函数检测、ANR监控、启动监控等,都需要对Framework有比较深入的了解,才能知道怎么去做监控,利用什么机制去监控,函数插桩插到哪里,反......
  • Jetpack系列-Lifecycle使用和源码分析
    1简介和简单使用1.1简介Lifecycle是Jetpack中一个生命周期感知型组件,可执行操作来响应另一个组件(如Activity和Fragment)的生命周期状态的变化。该组件通过感知Activity和Fragment的生命周期事件,在内部维护一个状态,该状态又可以转换成生命周期事件。主要作用就是进行系统组件......
  • 阿里P7架构师整理:最新Android 开发源码精编内核解析
    做Android开发多年,我们都深知阅读源码的重要性,阅读源码可以帮助我们:①在通用型基础技术中提高技术能力,凸显出自己的技术实力;②在重点领域打造自己的亮点,参与技术栈的运维,积累丰富的使用经验,成为团队的核心骨干;③从优秀的源码中学习设计模式的应用,和有用的编码技巧。但是平时读源码......
  • EventBus 源码分析 - 注解 + 反射
    EventBus源码解析随着LiveData和KotlinFlow的出现,EventBus已经慢慢过时了。不过EventBus源码的设计思想以及实现原理还是值得我们去学习的。getDefault()方法EventBus().getDefault().register(this)首先EventBus的创建用到了DCL单例模式,源码如下:publicclassEventB......
  • Android 换肤之资源(Resources)加载源码分析(一)
    本系列计划3篇:Android换肤之资源(Resources)加载(一)—本篇setContentView()/LayoutInflater源码分析(二)换肤框架搭建(三)看完本篇你可以学会什么?Resources在什么时候被解析并加载的Application#ResourcesActivity#Resourcesdrawable如何加载出来的创建自己的Resources加......
  • memcpy源码
    【调用栈】 【代码】 【glibc2.17和2.18性能的讨论】https://sourceware.org/bugzilla/show_bug.cgi?id=24872......