mixvideo-v2/apps/desktop/src/docs/markdown-parser-guide.md

9.3 KiB
Raw Blame History

Tree-sitter Markdown解析器使用指南

概述

基于Tree-sitter的Markdown解析器是一个强大的工具能够解析Markdown文档并保留原文引用信息。它提供了语法树解析、节点查询、位置信息提取等功能。

特性

  • 精确解析: 基于Tree-sitter的语法树解析比传统正则表达式更准确
  • 原文引用: 保留每个节点的精确位置信息(行号、列号、字符偏移)
  • 错误恢复: 即使在语法错误存在时也能提供有用的解析结果
  • 多种查询: 支持标题、链接、代码块等特定节点类型的查询
  • 文档验证: 检查文档结构的一致性和潜在问题
  • 实时解析: 支持实时解析和增量更新

快速开始

1. 基本解析

import { markdownService } from '../services/markdownService';

// 解析Markdown文档
const markdown = `
# 标题

这是一个**粗体**文本和一个[链接](https://example.com)。

## 子标题

- 列表项1
- 列表项2

\`\`\`javascript
console.log('Hello, World!');
\`\`\`
`;

try {
  const result = await markdownService.parseMarkdown(markdown);
  console.log('解析结果:', result);
  console.log('统计信息:', result.statistics);
} catch (error) {
  console.error('解析失败:', error);
}

2. 使用React组件

import React from 'react';
import MarkdownParserRenderer from '../components/MarkdownParserRenderer';

const MyComponent: React.FC = () => {
  const markdown = `
# 我的文档

这是一个示例文档。

## 功能特性

- 支持**粗体**和*斜体*
- 支持[链接](https://example.com)
- 支持代码块

\`\`\`typescript
const greeting = "Hello, World!";
console.log(greeting);
\`\`\`
  `;

  const handleNodeClick = (node) => {
    console.log('点击的节点:', node);
  };

  const handleParseComplete = (result) => {
    console.log('解析完成:', result);
  };

  return (
    <MarkdownParserRenderer
      content={markdown}
      showOutline={true}
      showLinks={true}
      showValidation={true}
      showPositionInfo={true}
      onNodeClick={handleNodeClick}
      onParseComplete={handleParseComplete}
    />
  );
};

API参考

MarkdownService

parseMarkdown(text: string, config?: MarkdownParserConfig)

解析Markdown文档并返回完整的解析结果。

参数:

  • text: 要解析的Markdown文本
  • config: 可选的解析器配置

返回: Promise<MarkdownParseResult>

extractOutline(text: string)

提取文档大纲(标题结构)。

参数:

  • text: Markdown文本

返回: Promise<OutlineItem[]>

extractLinks(text: string)

提取文档中的所有链接和图片。

参数:

  • text: Markdown文本

返回: Promise<LinkInfo[]>

validateMarkdown(text: string)

验证Markdown文档结构。

参数:

  • text: Markdown文本

返回: Promise<ValidationResult>

queryNodes(text: string, queryType: QueryType | string)

查询特定类型的节点。

参数:

  • text: Markdown文本
  • queryType: 查询类型('headings', 'links', 'code'

返回: Promise<MarkdownNode[]>

findNodeAtPosition(text: string, line: number, column: number)

根据位置查找节点。

参数:

  • text: Markdown文本
  • line: 行号从0开始
  • column: 列号从0开始

返回: Promise<MarkdownNode | null>

MarkdownParserRenderer组件

属性

  • content: string - Markdown文本内容
  • config?: MarkdownParserConfig - 解析器配置
  • showOutline?: boolean - 是否显示大纲
  • showLinks?: boolean - 是否显示链接列表
  • showValidation?: boolean - 是否显示验证结果
  • showPositionInfo?: boolean - 是否显示位置信息
  • enableRealTimeParsing?: boolean - 是否启用实时解析
  • className?: string - 自定义样式类名
  • onNodeClick?: (node: MarkdownNode) => void - 节点点击回调
  • onParseComplete?: (result: MarkdownParseResult) => void - 解析完成回调

高级用法

1. 自定义解析器配置

const config: MarkdownParserConfig = {
  preserve_whitespace: true,
  parse_inline_html: false,
  max_depth: 50,
  timeout_ms: 5000,
};

const result = await markdownService.parseMarkdown(markdown, config);

2. 查询特定节点

// 查询所有标题
const headings = await markdownService.queryHeadings(markdown);

// 查询所有链接
const links = await markdownService.queryLinkNodes(markdown);

// 查询所有代码块
const codeBlocks = await markdownService.queryCodeBlocks(markdown);

3. 位置信息处理

const result = await markdownService.parseMarkdown(markdown);

// 遍历所有节点并打印位置信息
function printNodePositions(node: MarkdownNode, depth = 0) {
  const indent = '  '.repeat(depth);
  const position = markdownService.formatRange(node.range.start, node.range.end);
  console.log(`${indent}${node.node_type} [${position}]: ${node.content.substring(0, 50)}...`);
  
  node.children.forEach(child => printNodePositions(child, depth + 1));
}

printNodePositions(result.root);

4. 文档验证

const validation = await markdownService.validateMarkdown(markdown);

if (!validation.is_valid) {
  console.log('文档存在问题:');
  validation.issues.forEach(issue => {
    console.log(`- ${issue.severity}: ${issue.message} (Line ${issue.range.start.line + 1})`);
  });
}

5. 实时编辑器集成

import React, { useState } from 'react';
import MarkdownParserRenderer from '../components/MarkdownParserRenderer';

const MarkdownEditor: React.FC = () => {
  const [content, setContent] = useState('# 开始编写...');

  return (
    <div className="flex h-screen">
      <div className="w-1/2 p-4">
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          className="w-full h-full p-4 border rounded font-mono"
          placeholder="在这里输入Markdown..."
        />
      </div>
      <div className="w-1/2 p-4 border-l">
        <MarkdownParserRenderer
          content={content}
          enableRealTimeParsing={true}
          showOutline={true}
          showValidation={true}
          showPositionInfo={true}
        />
      </div>
    </div>
  );
};

错误处理

try {
  const result = await markdownService.parseMarkdown(markdown);
  // 处理成功结果
} catch (error) {
  if (error.message.includes('timeout')) {
    console.error('解析超时,文档可能过大');
  } else if (error.message.includes('Parser not initialized')) {
    console.error('解析器初始化失败');
  } else {
    console.error('解析失败:', error.message);
  }
}

性能优化

1. 缓存解析结果

const cache = new Map<string, MarkdownParseResult>();

async function cachedParse(markdown: string) {
  const hash = btoa(markdown); // 简单哈希
  
  if (cache.has(hash)) {
    return cache.get(hash)!;
  }
  
  const result = await markdownService.parseMarkdown(markdown);
  cache.set(hash, result);
  
  return result;
}

2. 防抖实时解析

import { useMemo, useCallback } from 'react';
import { debounce } from 'lodash';

const useDebounceMarkdownParse = (content: string, delay = 300) => {
  const debouncedParse = useCallback(
    debounce(async (text: string) => {
      try {
        return await markdownService.parseMarkdown(text);
      } catch (error) {
        console.error('Parse error:', error);
        return null;
      }
    }, delay),
    [delay]
  );

  return useMemo(() => debouncedParse(content), [content, debouncedParse]);
};

最佳实践

  1. 错误处理: 始终使用try-catch包装解析调用
  2. 性能: 对于大文档,考虑使用分页或虚拟滚动
  3. 缓存: 缓存解析结果以避免重复计算
  4. 防抖: 在实时编辑场景中使用防抖
  5. 验证: 定期验证文档结构以确保质量
  6. 位置信息: 利用位置信息实现精确的错误定位和导航

故障排除

常见问题

  1. 解析器初始化失败

    • 检查Tauri后端是否正常运行
    • 确认tree-sitter-markdown依赖已正确安装
  2. 解析超时

    • 增加timeout_ms配置值
    • 考虑分块处理大文档
  3. 内存使用过高

    • 减少max_depth配置值
    • 清理不需要的解析结果缓存
  4. 位置信息不准确

    • 确保文本编码一致UTF-8
    • 检查换行符格式LF vs CRLF

调试技巧

// 启用详细日志
const config: MarkdownParserConfig = {
  // ... 其他配置
};

// 检查解析统计
const result = await markdownService.parseMarkdown(markdown, config);
console.log('解析统计:', result.statistics);

// 验证文档结构
const validation = await markdownService.validateMarkdown(markdown);
console.log('验证结果:', validation);

扩展功能

自定义查询

虽然当前版本提供了预定义的查询类型,但可以通过修改后端代码添加自定义查询:

// 在markdown_parser.rs中添加新的查询
let custom_query = Query::new(language, r#"
    (blockquote) @quote
    (table) @table
"#)?;
queries.insert("custom".to_string(), custom_query);

插件系统

考虑实现插件系统以支持:

  • 自定义节点渲染器
  • 语法扩展
  • 自定义验证规则
  • 导出格式支持

这个解析器为Markdown文档处理提供了强大而灵活的基础可以根据具体需求进行扩展和定制。