Rust HTML解析库tree-sitter-html的使用,高效语法分析与DOM树构建工具

tree-sitter-html

用于tree-sitter的HTML语法。

参考

  • HTML5规范

安装命令:

cargo add tree-sitter-html

或在Cargo.toml中添加:

tree-sitter-html = "0.23.2"

完整示例代码:

use tree_sitter::Parser;
use tree_sitter_html::language;

fn main() {
    // 创建解析器
    let mut parser = Parser::new();
    
    // 设置HTML语言
    parser.set_language(language()).expect("Error loading HTML grammar");
    
    // HTML源代码
    let source_code = r#"
        <!DOCTYPE html>
        <html>
        <head>
            <title>示例页面</title>
        </head>
        <body>
            <h1>欢迎使用tree-sitter-html</h1>
            <p>这是一个HTML解析示例</p>
        </body>
        </html>
    "#;
    
    // 解析HTML
    let tree = parser.parse(source_code, None).unwrap();
    
    // 获取根节点
    let root_node = tree.root_node();
    
    // 打印语法树结构
    println!("语法树:");
    print_tree(&root_node, source_code, 0);
    
    // 遍历所有元素节点
    println!("\n所有元素节点:");
    traverse_nodes(&root_node, source_code);
}

fn print_tree(node: &tree_sitter::Node, source: &str, depth: usize) {
    let indent = "  ".repeat(depth);
    let node_type = node.kind();
    let node_text = node.utf8_text(source.as_bytes()).unwrap_or("");
    
    println!("{}{}: '{}'", indent, node_type, node_text);
    
    // 递归打印子节点
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i) {
            print_tree(&child, source, depth + 1);
        }
    }
}

fn traverse_nodes(node: &tree_sitter::Node, source: &str) {
    if node.kind() == "element" || node.kind() == "start_tag" {
        let text = node.utf8_text(source.as_bytes()).unwrap_or("");
        println!("{}: {}", node.kind(), text);
    }
    
    // 递归遍历子节点
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i) {
            traverse_nodes(&child, source);
        }
    }
}

这个示例展示了如何使用tree-sitter-html库来解析HTML代码,构建语法树,并遍历节点结构。代码包含了完整的解析过程和树结构打印功能。


1 回复

Rust HTML解析库tree-sitter-html的使用指南

概述

tree-sitter-html是一个基于Tree-sitter语法分析器的HTML解析库,专门为Rust语言设计。它能够高效解析HTML文档,构建精确的语法树,并提供DOM树构建功能。

主要特性

  • 高性能的增量解析
  • 精确的错误恢复机制
  • 支持HTML5标准
  • 提供丰富的节点查询API
  • 内存安全的Rust实现

安装方法

在Cargo.toml中添加依赖:

[dependencies]
tree-sitter = "0.20"
tree-sitter-html = "0.19"

基本使用示例

use tree_sitter::Parser;
use tree_sitter_html::language;

fn main() {
    // 创建解析器
    let mut parser = Parser::new();
    
    // 设置HTML语言
    parser.set_language(language()).unwrap();
    
    // 解析HTML内容
    let html_content = r#"
        <!DOCTYPE html>
        <html>
        <head>
            <title>示例页面</title>
        </head>
        <body>
            <h1>Hello World</h1>
            <p>这是一个段落</p>
        </body>
        </html>
    "#;
    
    let tree = parser.parse(html_content, None).unwrap();
    
    // 获取根节点
    let root_node = tree.root_node();
    
    // 遍历语法树
    let mut cursor = root_node.walk();
    traverse_tree(&mut cursor, 0);
}

fn traverse_tree(cursor: &mut tree_sitter::TreeCursor, depth: usize) {
    let node = cursor.node();
    let indent = "  ".repeat(depth);
    
    println!("{}{:?}: {}", indent, node.kind(), node.utf8_text(cursor.node().kind().as_bytes()).unwrap_or(""));
    
    if cursor.goto_first_child() {
        traverse_tree(cursor, depth + 1);
        while cursor.goto_next_sibling() {
            traverse_tree(cursor, depth + 1);
        }
        cursor.goto_parent();
    }
}

查询节点示例

use tree_sitter::{Query, QueryCursor};

fn query_elements() {
    let html = r#"<div class="container"><p>内容</p></div>"#;
    
    let mut parser = Parser::new();
    parser.set_language(language()).unwrap();
    let tree = parser.parse(html, None).unwrap();
    
    // 创建查询来查找所有元素
    let query = Query::new(
        language(),
        "(element (start_tag (tag_name) @tag) @element"
    ).unwrap();
    
    let mut query_cursor = QueryCursor::new();
    let matches = query_cursor.matches(&query, tree.root_node(), html.as_bytes());
    
    for mat in matches {
        for capture in mat.captures {
            let node = capture.node;
            println!("找到元素: {}", node.utf8_text(html.as_bytes()).unwrap());
        }
    }
}

错误处理

fn parse_with_error_handling(html: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut parser = Parser::new();
    parser.set_language(language())?;
    
    match parser.parse(html, None) {
        Ok(tree) => {
            // 检查是否有语法错误
            if tree.root_node().has_error() {
                eprintln!("解析完成,但包含语法错误");
            }
            Ok(())
        }
        Err(e) => {
            eprintln!("解析失败: {}", e);
            Err(Box::new(e))
        }
    }
}

完整示例demo

use tree_sitter::{Parser, Query, QueryCursor};
use tree_sitter_html::language;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // 示例HTML内容
    let html_content = r#"
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <title>示例页面</title>
            <style>
                body { font-family: Arial, sans-serif; }
            </style>
        </head>
        <body>
            <div class="container">
                <h1>欢迎使用tree-sitter-html</h1>
                <p>这是一个使用Rust解析HTML的示例</p>
                <ul>
                    <li>项目1</li>
                    <li>项目2</li>
                    <li>项目3</li>
                </ul>
            </div>
            <script>
                console.log("页面加载完成");
            </script>
        </body>
        </html>
    "#;

    // 创建解析器实例
    let mut parser = Parser::new();
    
    // 设置HTML语言
    parser.set_language(language())?;
    
    // 解析HTML内容
    let tree = parser.parse(html_content, None)?;
    
    // 检查解析结果
    let root_node = tree.root_node();
    println!("解析完成,根节点类型: {:?}", root_node.kind());
    
    // 检查是否有语法错误
    if root_node.has_error() {
        eprintln!("警告:HTML包含语法错误");
    }
    
    // 遍历语法树
    println!("\n=== 语法树遍历 ===");
    let mut cursor = root_node.walk();
    traverse_tree(&mut cursor, 0);
    
    // 查询特定元素
    println!("\n=== 元素查询 ===");
    query_specific_elements(html_content)?;
    
    // 错误处理示例
    println!("\n=== 错误处理示例 ===");
    let invalid_html = "<div><p>未闭合的标签";
    parse_with_error_handling(invalid_html)?;
    
    Ok(())
}

/// 递归遍历语法树
fn traverse_tree(cursor: &mut tree_sitter::TreeCursor, depth: usize) {
    let node = cursor.node();
    let indent = "  ".repeat(depth);
    
    // 输出节点信息
    let node_text = node.utf8_text(cursor.node().kind().as_bytes()).unwrap_or("");
    println!("{}{:?}: '{}'", indent, node.kind(), node_text);
    
    // 递归遍历子节点
    if cursor.goto_first_child() {
        traverse_tree(cursor, depth + 1);
        while cursor.goto_next_sibling() {
            traverse_tree(cursor, depth + 1);
        }
        cursor.goto_parent();
    }
}

/// 查询特定HTML元素
fn query_specific_elements(html: &str) -> Result<(), Box<dyn Error>> {
    let mut parser = Parser::new();
    parser.set_language(language())?;
    let tree = parser.parse(html, None)?;
    
    // 查询所有标题元素
    let query = Query::new(
        language(),
        "(element (start_tag (tag_name) @tag_name) @element"
    )?;
    
    let mut query_cursor = QueryCursor::new();
    let matches = query_cursor.matches(&query, tree.root_node(), html.as_bytes());
    
    println!("找到的元素:");
    for mat in matches {
        for capture in mat.captures {
            let node = capture.node;
            if node.kind() == "tag_name" {
                let tag_name = node.utf8_text(html.as_bytes())?;
                println!("  - 标签名: {}", tag_name);
            }
        }
    }
    
    Ok(())
}

/// 带错误处理的解析函数
fn parse_with_error_handling(html: &str) -> Result<(), Box<dyn Error>> {
    let mut parser = Parser::new();
    parser.set_language(language())?;
    
    match parser.parse(html, None) {
        Ok(tree) => {
            if tree.root_node().has_error() {
                println!("解析完成,但文档包含语法错误");
                // 可以在这里添加更详细的错误处理逻辑
            } else {
                println!("文档解析成功");
            }
            Ok(())
        }
        Err(e) => {
            eprintln!("解析失败: {}", e);
            Err(Box::new(e))
        }
    }
}

/// 性能优化示例:重用解析器实例
struct HtmlParser {
    parser: Parser,
}

impl HtmlParser {
    fn new() -> Result<Self, Box<dyn Error>> {
        let mut parser = Parser::new();
        parser.set_language(language())?;
        Ok(Self { parser })
    }
    
    fn parse(&mut self, html: &str) -> Result<tree_sitter::Tree, Box<dyn Error>> {
        Ok(self.parser.parse(html, None)?)
    }
}

性能优化提示

  1. 重用Parser实例以避免重复分配
  2. 对于大文件,使用增量解析功能
  3. 合理使用查询缓存
  4. 及时释放不再使用的语法树

注意事项

  • 确保输入的HTML编码为UTF-8
  • 处理不完整HTML时注意错误恢复机制
  • 对于生产环境,建议添加适当的错误处理和日志记录

这个库特别适合需要高性能HTML解析的场景,如网页爬虫、静态网站生成器、代码编辑器等工具的开发。

回到顶部