Cherry Markdown语法基础:抽象语法类设计与实现

Cherry Markdown语法基础:抽象语法类设计与实现

【免费下载链接】cherry-markdown ✨ A Markdown Editor 【免费下载链接】cherry-markdown 项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-markdown

引言:现代Markdown引擎的架构挑战

你是否曾思考过,一个现代化的Markdown编辑器如何高效解析数百种语法规则,同时保持优秀的性能和扩展性?Cherry Markdown通过其精妙的抽象语法类设计,为这个问题提供了优雅的解决方案。

本文将深入解析Cherry Markdown的核心语法类架构,揭示其如何通过面向对象设计和多态机制实现高效的Markdown解析。读完本文,你将掌握:

  • Cherry Markdown的三层语法抽象架构
  • 语法类的生命周期管理和缓存机制
  • 正则表达式规则的定义与编译策略
  • 段落级与句子级语法的差异与协作
  • 自定义语法扩展的最佳实践

语法类体系架构

Cherry Markdown采用分层架构设计,将语法处理分为三个核心层次:

mermaid

核心基类:SyntaxBase

SyntaxBase是所有语法类的基类,定义了统一的接口和生命周期方法:

export default class SyntaxBase {
  static HOOK_NAME = 'default';
  static HOOK_TYPE = HOOKS_TYPE_LIST.DEFAULT;
  
  constructor(editorConfig) {
    this.RULE = this.rule(editorConfig);
  }

  // 生命周期方法
  beforeMakeHtml(str) { return str; }
  makeHtml(str) { return str; }
  afterMakeHtml(str) { return str; }
  
  // 规则测试
  test(str) {
    return this.RULE.reg ? this.RULE.reg.test(str) : false;
  }
  
  // 规则定义抽象方法
  rule(editorConfig) {
    return { begin: '', end: '', content: '', reg: new RegExp('') };
  }
}

段落级语法基类:ParagraphBase

ParagraphBase继承自SyntaxBase,专门处理段落级语法元素,引入了缓存机制来优化性能:

export default class ParagraphBase extends SyntaxBase {
  static HOOK_TYPE = HOOKS_TYPE_LIST.PAR;
  static IN_PARAGRAPH_CACHE_KEY_PREFIX = '!';
  
  constructor({ needCache, defaultCache } = { needCache: false }) {
    super({});
    this.needCache = !!needCache;
    if (needCache) {
      this.cache = defaultCache || {};
      this.cacheKey = `~~C${cacheCounter}`;
      cacheCounter += 1;
    }
  }
  
  // 缓存管理方法
  pushCache(str, sign = '', lineCount = 0) {
    const $sign = sign || this.$engine.hash(str);
    const key = `${this.cacheKey}I${$sign}_L${lineCount}$`;
    this.cache[$sign] = { content: str, key };
    return key;
  }
  
  popCache(sign) {
    return this.cache[sign].content || '';
  }
  
  restoreCache(html) {
    const regex = new RegExp(
      `${this.cacheKey}I((?:${ParagraphBase.IN_PARAGRAPH_CACHE_KEY_PREFIX_REGEX})?\\w+)\\$`,
      'g'
    );
    return html.replace(regex, (match, cacheSign) => 
      this.popCache(cacheSign.replace(/_L\d+$/, ''))
    );
  }
}

语法规则定义与编译

Cherry Markdown采用结构化的规则定义方式,支持多种语法风格:

规则定义结构

// ATX标题语法规则定义
const atx = {
  begin: '(?:^|\\n)(\\n*)(?:\\h*(#{1,6}))', // 匹配行首和标题级别
  content: '(.+?)',                         // 匹配标题内容
  end: '(?:\\h*#*\\h*)?(?=$|\\n)',         // 匹配行尾
  reg: compileRegExp(atx, 'g', true)       // 编译后的正则表达式
};

// Setext标题语法规则  
const setext = {
  begin: '(?:^|\\n)(\\n*)',
  content: [
    '(?:\\h*',
    '(.+)',     // 标题文本
    ')\\n',
    '(?:\\h*',
    '([=]+|[-]+)', // 下划线级别
    ')'
  ].join(''),
  end: '(?=$|\\n)',
  reg: compileRegExp(setext, 'g', true)
};

正则表达式编译策略

Cherry Markdown使用专门的编译函数处理规则定义:

function compileRegExp(rule, flags = 'g', namedGroup = false) {
  let pattern = rule.begin + rule.content + rule.end;
  if (namedGroup) {
    // 支持命名捕获组
    pattern = pattern.replace(/\(\?<(\w+)>/g, '(?<$1>');
  }
  return new RegExp(pattern, flags);
}

语法处理生命周期

每个语法类都遵循严格的生命周期管理:

三阶段处理流程

mermaid

具体实现示例:标题语法

以Header类为例,展示完整的三阶段处理:

export default class Header extends ParagraphBase {
  static HOOK_NAME = 'header';

  // 阶段1: 预处理和缓存
  beforeMakeHtml(str) {
    let $str = str;
    if (this.test($str, ATX_HEADER)) {
      $str = $str.replace(this.RULE[ATX_HEADER].reg, (match, lines, level, text) => {
        if (text.trim() === '') return match;
        return this.getCacheWithSpace(this.pushCache(match), match, true);
      });
    }
    return $str;
  }

  // 阶段2: 实际转换
  makeHtml(str, sentenceMakeFunc) {
    let $str = this.restoreCache(str);
    if (this.test($str, ATX_HEADER)) {
      $str = $str.replace(this.RULE[ATX_HEADER].reg, (match, lines, level, text) => {
        const lineCount = calculateLinesOfParagraph(lines, this.getLineCount(match));
        const $text = text.replace(/\s+#+\s*$/, '');
        const { html: result, sign } = this.$wrapHeader($text, level.length, lineCount, sentenceMakeFunc);
        return this.getCacheWithSpace(this.pushCache(result, sign, lineCount), match, true);
      });
    }
    return $str;
  }

  // 阶段3: 后处理
  afterMakeHtml(html) {
    const $html = super.afterMakeHtml(html);
    this.headerIDCache = []; // 清理ID缓存
    this.headerIDCounter = {};
    return $html;
  }
}

高级特性:ID生成与锚点处理

Cherry Markdown提供了智能的标题ID生成机制:

ID生成算法

$generateId(headerText, toLowerCase = true) {
  const len = headerText.length;
  let id = '';
  for (let i = 0; i < len; i++) {
    const c = headerText.charAt(i);
    if (alphabetic.test(c)) {
      id += toLowerCase ? c.toLowerCase() : c;
    } else if (numeric.test(c)) {
      id += c;
    } else if (toDashChars.test(c)) {
      id += id.length < 1 || id.charAt(id.length - 1) !== '-' ? '-' : '';
    } else if (c.charCodeAt(0) > 255) {
      try { id += encodeURIComponent(c); } catch (error) {}
    }
  }
  return id;
}

// 重复ID处理
generateIDNoDup(headerText) {
  let newId = this.$generateId(headerText, true);
  const idIndex = this.headerIDCache.indexOf(newId);
  if (idIndex !== -1) {
    this.headerIDCounter[idIndex] += 1;
    newId += `-${this.headerIDCounter[idIndex] + 1}`;
  } else {
    const newIndex = this.headerIDCache.push(newId);
    this.headerIDCounter[newIndex - 1] = 1;
  }
  return newId;
}

锚点包装逻辑

$wrapHeader(text, level, dataLines, sentenceMakeFunc) {
  const processedText = sentenceMakeFunc(text.trim());
  let { html } = processedText;
  
  // 自定义ID提取
  const customIDRegex = /\s+\{#([A-Za-z0-9-]+)\}$/;
  const idMatch = html.match(customIDRegex);
  let anchorID;
  
  if (idMatch !== null) {
    html = html.substring(0, idMatch.index);
    [, anchorID] = idMatch;
  }
  
  const headerTextRaw = this.$parseTitleText(html);
  if (!anchorID) {
    anchorID = this.generateIDNoDup(headerTextRaw.replace(/~fn#([0-9]+)#/g, ''));
  }
  
  const safeAnchorID = `safe_${anchorID}`;
  const sign = this.$engine.hash(`${level}-${processedText.sign}-${anchorID}-${dataLines}`);
  
  return {
    html: `<h${level} id="${safeAnchorID}" data-sign="${sign}" data-lines="${dataLines}">
             ${this.$getAnchor(anchorID)}${html}
           </h${level}>`,
    sign: `${sign}`
  };
}

性能优化策略

缓存机制对比

缓存类型适用场景优点缺点
段落缓存块级元素(标题、代码块等)减少重复解析,提升性能增加内存使用
行内缓存句子级元素(加粗、斜体等)精细控制,内存友好解析开销较大
签名缓存唯一内容标识避免重复处理相同内容哈希计算开销

换行处理策略

Cherry Markdown支持两种换行模式:

initBrReg(classicBr = false) {
  this.classicBr = classicBr;
  // 经典模式:两个以上换行才分割段落
  // 现代模式:一个换行转<br>,两个换行分割段落
}

$cleanParagraph(str) {
  const trimedPar = str.replace(/^\n+/, '').replace(/\n+$/, '');
  if (this.classicBr) {
    return trimedPar;
  }
  return this.joinRawHtml(trimedPar).replace(/\n/g, '<br>').replace(/\r/g, '\n');
}

自定义语法扩展指南

基于Cherry Markdown的抽象类体系,扩展自定义语法变得非常简单:

基本扩展步骤

  1. 选择基类:根据语法特性选择SyntaxBaseParagraphBase
  2. 定义规则:实现rule()方法返回正则表达式规则
  3. 实现转换:重写makeHtml()方法处理语法转换
  4. 注册钩子:使用Cherry.useHook()注册自定义语法

示例:自定义警告框语法

import ParagraphBase from '@/core/ParagraphBase';

export default class AlertBlock extends ParagraphBase {
  static HOOK_NAME = 'alert';
  
  rule() {
    return {
      begin: '(?:^|\\n)(\\n*)!!!\\s*(\\w+)\\s*\\n',
      content: '([\\s\\S]+?)',
      end: '\\n!!!\\s*\\n',
      reg: new RegExp(this.begin + this.content + this.end, 'g')
    };
  }
  
  makeHtml(str, sentenceMakeFunc) {
    return str.replace(this.RULE.reg, (match, lines, type, content) => {
      const processedContent = sentenceMakeFunc(content.trim());
      return `<div class="alert alert-${type}">${processedContent.html}</div>`;
    });
  }
}

// 注册自定义语法
Cherry.useHook(AlertBlock);

总结与最佳实践

Cherry Markdown的抽象语法类设计体现了现代前端架构的多个重要原则:

设计原则总结

  1. 单一职责原则:每个语法类只负责一种语法元素的处理
  2. 开闭原则:通过继承和钩子机制支持扩展,避免修改现有代码
  3. 依赖倒置原则:高层模块不依赖低层模块,都依赖抽象接口
  4. 接口隔离原则:细粒度的生命周期接口,避免冗余实现

性能优化建议

  • 合理使用缓存:对重复内容使用签名缓存,对复杂解析使用结果缓存
  • 避免过度嵌套:控制正则表达式的复杂度,避免回溯爆炸
  • 批量处理:在afterMakeHtml阶段进行批量后处理操作
  • 内存管理:及时清理不再使用的缓存内容

扩展性考虑

  • 插件化架构:所有语法都以插件形式存在,支持动态加载和卸载
  • 配置驱动:通过配置对象控制语法特性和行为
  • 类型安全:完整的TypeScript类型定义,提高开发体验

Cherry Markdown的语法类体系不仅提供了强大的Markdown解析能力,更为开发者提供了清晰的扩展路径和性能优化空间。通过深入理解这一架构,你可以更好地定制和优化自己的Markdown处理需求,构建出更加强大和高效的文档处理应用。

无论是构建下一代文档编辑器、开发技术博客平台,还是实现企业级内容管理系统,Cherry Markdown的抽象语法类设计都将为你提供坚实的技术基础。

【免费下载链接】cherry-markdown ✨ A Markdown Editor 【免费下载链接】cherry-markdown 项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-markdown

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值