Rust标记处理库tagu的使用,高效解析与操作HTML/XML标签的轻量级工具

Rust标记处理库tagu的使用,高效解析与操作HTML/XML标签的轻量级工具

tagu是一个通过将结构体链式连接或使用闭包来编程构建XML/HTML/SVG的Rust库。它不使用模板引擎,而是编写"看起来像"Rust的数据和标记。用户可以通过inline()函数控制格式化输出。

适配器示例

use tagu::build;
use tagu::prelude::*;

fn main() -> std::fmt::Result {
    let a = build::elem("a");
    let b = build::elem("b");
    let c = build::elem("c");
    let it = build::from_iter((0..5).map(|i| build::elem(format_move!("x{}", i)).inline()));
    let all = a.append(b.append(c.append(it)));

    tagu::render(all, tagu::stdout_fmt())
}

输出文本

<a>
    <b>
        <c>
            <x0></x0>
            <x1></x1>
            <x2></x2>
            <x3></x3>
            <x4></x4>
        </c>
    </b>
</a>

完整示例代码

use tagu::build;
use tagu::prelude::*;

fn main() -> std::fmt::Result {
    // 创建基础HTML元素
    let html = build::elem("html");
    let head = build::elem("head");
    let body = build::elem("body");
    
    // 创建标题元素
    let title = build::elem("title").append("My Page");
    
    // 创建3个段落元素
    let paragraphs = build::from_iter((1..=3).map(|i| 
        build::elem("p")
            .with(("id", format_move!("p{}", i)))  // 设置id属性
            .append(format_move!("Paragraph {}", i))  // 设置内容
    ));
    
    // 构建完整HTML结构
    let document = html
        .append(head.append(title))  // 将标题放入head
        .append(body.append(paragraphs));  // 将段落放入body
    
    // 渲染输出到标准输出
    tagu::render(document, tagu::stdout_fmt())
}

堆栈示例

use tagu::build;
use tagu::prelude::*;

fn main() -> std::fmt::Result {
    let all = build::from_stack(|stack| {
        let a = build::elem("a");
        let b = build::elem("b");
        let c = build::elem("c").with_tab("→");  // 设置自定义缩进符号

        // 将元素推入堆栈
        let mut stack = stack.push(a)?.push(b)?.push(c)?;

        // 添加5个内联元素
        for i in 0..5 {
            let e = build::elem(format_move!("x{}", i)).inline();
            stack.put(e)?;
        }
        // 弹出所有元素
        stack.pop()?.pop()?.pop()
    });

    // 渲染输出,使用空格作为缩进
    tagu::render(all.with_tab(" "), tagu::stdout_fmt())
}

输出文本

<a>
 <b>
→→<c>
→→→<x0></x0>
→→→<x1></x1>
→→→<x2></x2>
→→→<x3></x3>
→→→<x4></x4>
→→</c>
 </b>
</a>

完整示例代码

use tagu::build;
use tagu::prelude::*;

fn main() -> std::fmt::Result {
    // 使用堆栈方式构建文档
    let document = build::from_stack(|stack| {
        // 创建html元素
        let html = build::elem("html");
        
        // 创建head和body元素,body使用双空格缩进
        let head = build::elem("head");
        let body = build::elem("body").with_tab("  ");
        
        // 将元素推入堆栈
        let mut stack = stack.push(html)?.push(head)?.push(body)?;
        
        // 添加标题
        stack.put(build::elem("title").append("My Stack Page"))?;
        
        // 添加3个段落
        for i in 1..=3 {
            let p = build::elem("p")
                .with(("id", format_move!("p{}", i)))  // 设置id属性
                .append(format_move!("Stack Paragraph {}", i));  // 设置内容
            stack.put(p)?;
        }
        
        // 弹出所有元素
        stack.pop()?.pop()?.pop()
    });

    // 渲染输出,使用空格作为缩进
    tagu::render(document.with_tab(" "), tagu::stdout_fmt())
}

方法选择建议

您可以通过构建长适配器链来追加元素,也可以即时将元素渲染到写入器。使用链式方法时,您不必担心处理错误,因为在链式连接时实际上不会写出任何内容。但是,您需要"倒置"地构建结构,即先构建最嵌套的元素,然后才能将其附加到外层元素上。

即时渲染时,您需要处理错误,但元素处理的顺序与渲染顺序匹配。您可以混合使用这两种方法,因为您可以从闭包创建元素,然后将这些元素链接在一起。

inline函数说明

默认情况下,标签会插入换行符和制表符。如果在元素上调用inline(),则该元素内的所有元素都将内联显示。

SVG示例

use tagu::build;
use tagu::prelude::*;

fn main() -> std::fmt::Result {
    let width = 100.0;
    let height = 100.0;

    // 创建矩形元素
    let rect = build::single("rect").with(attrs!(
        ("x1", 0),
        ("y1", 0),
        ("rx", 20),
        ("ry", 20),
        ("width", width),
        ("height", height),
        ("style", "fill:blue")
    ));

    // 创建内联样式元素
    let style = build::elem("style")
        .inline()
        .append(build::raw(".test{fill:none;stroke:white;stroke-width:3}"));

    // 创建SVG元素
    let svg = build::elem("svg").with(attrs!(
        ("xmlns", "http://www.w3.org/2000/svg"),
        ("viewBox", format_move!("0 0 {} {}", width, height))
    ));

    // 使用堆栈创建图形元素
    let rows = build::from_stack(|mut f| {
        for r in (0..50).step_by(5) {
            if r % 10 == 0 {
                // 创建圆形
                let c = build::single("circle").with(attrs!(("cx", 50.0), ("cy", 50.0), ("r", r)));
                f.put(c)?;
            } else {
                // 创建矩形
                let r = build::single("rect").with(attrs!(
                    ("x", 50 - r),
                    ("y", 50 - r),
                    ("width", r * 2),
                    ("height", r * 2)
                ));
                f.put(r)?;
            }
        }
        Ok(f)
    });

    // 将图形分组
    let table = build::elem("g").with(("class", "test")).append(rows);

    // 组合所有元素
    let all = svg.append(style).append(rect).append(table);

    // 渲染输出
    tagu::render(all, tagu::stdout_fmt())
}

输出

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <style>.test{fill:none;stroke:white;stroke-width:3}</style>
    <rect x1="0" y1="0" rx="20" ry="20" width="100" height="100" style="fill:blue"/>
    <g class="test">
            <circle cx="50" cy="50" r="0"/>
            <rect x="45" y="45" width="10" height="10"/>
            <circle cx="50" cy="50" r="10"/>
            <rect x="35" y="35" width="30" height="30"/>
            <circle cx="50" cy="50" r="20"/>
            <rect x="25" y="25" width="50" height="50"/>
            <circle cx="50" cy="50" r="30"/>
            <rect x="15" y="15" width="70" height="70"/>
            <circle cx="50" cy="50" r="40"/>
            <rect x="5" y="5" width="90" height="90"/>
    </g>
</svg>

完整示例代码

use tagu::build;
use tagu::prelude::*;

fn main() -> std::fmt::Result {
    // 创建SVG画布
    let svg = build::elem("svg").with(attrs!(
        ("xmlns", "http://www.w3.org/2000/svg"),
        ("width", "200"),
        ("height", "200"),
        ("viewBox", "0 0 200 200")
    ));
    
    // 创建绿色圆形
    let circle = build::single("circle").with(attrs!(
        ("cx", "100"),
        ("cy", "100"),
        ("r", "80"),
        ("fill", "green")
    ));
    
    // 创建白色文本
    let text = build::elem("text")
        .with(attrs!(
            ("x", "100"),
            ("y", "125"),
            ("font-size", "60"),
            ("text-anchor", "middle"),
            ("fill", "white")
        ))
        .append("SVG");
    
    // 组合所有元素
    let document = svg.append(circle).append(text);
    
    // 渲染输出
    tagu::render(document, tagu::stdout_fmt())
}

安装方法

在项目目录中运行以下Cargo命令:

cargo add tagu

或者将以下行添加到您的Cargo.toml:

tagu = "0.1.6"

1 回复

Rust标记处理库tagu的使用指南

介绍

tagu是一个轻量级的Rust库,专门用于高效解析和操作HTML/XML标签。它提供了一种简单而强大的方式来构建、解析和操作标记语言文档,特别适合需要处理HTML/XML但又不想引入重型解析器的场景。

tagu的主要特点:

  • 轻量级,无额外依赖
  • 高性能的标签解析和处理
  • 支持构建和修改DOM结构
  • 提供便捷的查询和遍历方法
  • 内存高效的设计

安装

在Cargo.toml中添加依赖:

[dependencies]
tagu = "0.3"

基本用法

1. 解析HTML/XML

use tagu::prelude::*;

let html = r#"<div class="container"><p>Hello, world!</p></div>"#;
let dom = tagu::parse(html).unwrap();

// 获取根元素
let root = dom.root();
println!("Root tag: {}", root.name()); // 输出: div

2. 构建DOM结构

use tagu::build::*;

let dom = elem("div")
    .with(attr("class", "container"))
    .with(elem("p").with(text("Hello, world!")))
    .build();

println!("{}", dom.to_string());
// 输出: <div class="container"><p>Hello, world!</p></div>

3. 查询元素

use tagu::query::*;

let html = r#"<div><p id="first">First</p><p>Second</p></div>"#;
let dom = tagu::parse(html).unwrap();

// 通过CSS选择器查找
let first_p = dom.select("p#first").next().unwrap();
println!("{}", first_p.text()); // 输出: First

// 查找所有p标签
for p in dom.select("p") {
    println!("{}", p.text());
}
// 输出:
// First
// Second

4. 修改DOM

use tagu::build::*;

let mut dom = tagu::parse(r#"<div><p>Original</p></div>"#).unwrap();
let p = dom.select("p").next().unwrap();

// 修改文本内容
p.set_text("Modified");

// 添加属性
p.set_attr("class", "highlight");

println!("{}", dom.to_string());
// 输出: <div><p class="highlight">Modified</p></div>

5. 遍历DOM

let html = r#"<div><p>First</p><p>Second</p></div>"#;
let dom = tagu::parse(html).unwrap();

// 深度优先遍历
dom.root().walk(|node| {
    if node.is_element() {
        println!("Element: {}", node.name());
    } else if node.is_text() {
        println!("Text: {}", node.text());
    }
});

高级用法

处理大型文档

use tagu::stream::*;

// 流式处理大型文档
let mut parser = tagu::stream::Parser::new();
let chunks = vec![
    "<div>".as_bytes(),
    "<p>Large".as_bytes(),
    " document</p></div>".as_bytes()
];

for chunk in chunks {
    parser.feed(chunk).unwrap();
}

let dom = parser.finish().unwrap();
println!("{}", dom.to_string());

自定义构建器

use tagu::build::*;

// 创建可重用的构建器
let mut builder = Builder::new();
builder.push(elem("html"));
builder.push(elem("head"));
builder.push(elem("title").with(text("My Page")));
builder.pop(); // 回到head
builder.pop(); // 回到html
builder.push(elem("body").with(text("Content")));

let dom = builder.build();
println!("{}", dom.to_string());

性能提示

  1. 对于静态内容,考虑使用tagu::static_dom!宏来避免运行时解析开销
  2. 处理大型文档时使用流式解析器
  3. 重用Builder实例来减少内存分配

总结

tagu提供了一个简单而强大的接口来处理HTML/XML文档,特别适合需要轻量级解决方案的场景。它的API设计直观,性能优异,是Rust生态中处理标记语言的一个优秀选择。

完整示例代码

use tagu::{prelude::*, build::*, query::*};

fn main() {
    // 示例1:解析HTML
    let html = r#"<div class="main"><h1>标题</h1><p>段落内容</p></div>"#;
    let dom = tagu::parse(html).unwrap();
    println!("解析后的DOM:\n{}", dom.to_string());

    // 示例2:构建DOM
    let new_dom = elem("html")
        .with(elem("head").with(elem("title").with(text("示例页面"))))
        .with(elem("body")
            .with(elem("div").with(attr("id", "content"))
                .with(elem("p").with(text("这是一个使用tagu构建的示例"))))
        )
        .build();
    println!("\n构建的DOM:\n{}", new_dom.to_string());

    // 示例3:查询和修改
    let mut dom = tagu::parse(r#"<ul><li>项目1</li><li>项目2</li></ul>"#).unwrap();
    for (i, li) in dom.select("li").enumerate() {
        li.set_text(format!("项目 {}", i + 10));
    }
    println!("\n修改后的列表:\n{}", dom.to_string());

    // 示例4:流式处理
    let mut parser = tagu::stream::Parser::new();
    let chunks = vec![
        "<table>".as_bytes(),
        "<tr><td>数据1</td>".as_bytes(),
        "<td>数据2</td></tr></table>".as_bytes()
    ];
    for chunk in chunks {
        parser.feed(chunk).unwrap();
    }
    let stream_dom = parser.finish().unwrap();
    println!("\n流式处理结果:\n{}", stream_dom.to_string());
}
回到顶部