9.3 KiB
9.3 KiB
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]);
};
最佳实践
- 错误处理: 始终使用try-catch包装解析调用
- 性能: 对于大文档,考虑使用分页或虚拟滚动
- 缓存: 缓存解析结果以避免重复计算
- 防抖: 在实时编辑场景中使用防抖
- 验证: 定期验证文档结构以确保质量
- 位置信息: 利用位置信息实现精确的错误定位和导航
故障排除
常见问题
-
解析器初始化失败
- 检查Tauri后端是否正常运行
- 确认tree-sitter-markdown依赖已正确安装
-
解析超时
- 增加timeout_ms配置值
- 考虑分块处理大文档
-
内存使用过高
- 减少max_depth配置值
- 清理不需要的解析结果缓存
-
位置信息不准确
- 确保文本编码一致(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文档处理提供了强大而灵活的基础,可以根据具体需求进行扩展和定制。