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());
性能提示
- 对于静态内容,考虑使用
tagu::static_dom!
宏来避免运行时解析开销 - 处理大型文档时使用流式解析器
- 重用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());
}