1 个不稳定版本
0.8.19 | 2024年2月13日 |
---|
#2404 在 解析器实现
61 每月下载量
255KB
4.5K SLoC
xml-no-std,是 xml-rs
的一个 no_std
分支
xml-no-std
是 Rust 编程语言的流行 XML 库 xml-rs
的 no_std
分支。该库为了符合 no_std
规范,牺牲了流式处理能力和性能(仍然需要 alloc
)。
所有功劳归于 netvl 和 kornelski。感谢你们出色的工作 💚
动机
xml-no-std
的创建是为了支持 XML 编码规则,用于 librasn
ASN.1 框架。由于 ASN.1 的各种编码规则,通常不会选择 XML 编码规则用于性能关键的应用场景。因此,性能损失是可以容忍的。
权衡
为了符合 no_std
环境,xml-no-std
使用 Iterator<Item = &u8>
进行读取和 alloc::string::String
进行写入,而不是 std::io::Read
和 std::io::Write
。因此不支持流式读取。
就性能而言,当读取具有许多属性的XML元素时,变更xml-no-std
会对性能产生重大影响。xml-no-std
使用alloc::collections::BTreeSet
来存储XML属性,这对于具有许多属性的元素来说不是最优的。这里肯定有改进的空间,因此欢迎贡献力量。
以下是我自己的开发机器上的一些大致数字
基准测试 | xml-rs |
xml-no-std |
---|---|---|
读取 | 43,255 ns/iter (+/- 1,498) | 57,263 ns/iter (+/- 1,121) |
读取大量属性 | 426,440 ns/iter (+/- 3,932) | 6,122,947 ns/iter (+/- 609,079) |
写入 | 7,405 ns/iter (+/- 31) | 17,303 ns/iter (+/- 134) |
构建和使用
xml-no-std使用Cargo,因此可以使用cargo add xml-no-std
或修改Cargo.toml
[dependencies]
xml-no-std = "0.8.16"
该包公开了一个名为xml-no-std
的单个crate。
读取XML文档
xml::reader::EventReader
需要一个Iterator
来遍历&u8
项目以读取。
EventReader
实现了IntoIterator
特质,因此可以直接在for
循环中使用它
use std::fs::File;
use std::io::BufReader;
use xml_no_std::reader::{EventReader, XmlEvent};
fn main() -> std::io::Result<()> {
let mut input = String::new();
let file = File::open("file.xml")?.read_to_string(&mut input);
let parser = EventReader::new(input.as_bytes().iter());
let mut depth = 0;
for e in parser {
match e {
Ok(XmlEvent::StartElement { name, .. }) => {
println!("{:spaces$}+{name}", "", spaces = depth * 2);
depth += 1;
}
Ok(XmlEvent::EndElement { name }) => {
depth -= 1;
println!("{:spaces$}-{name}", "", spaces = depth * 2);
}
Err(e) => {
eprintln!("Error: {e}");
break;
}
// There's more: https://docs.rs/xml-rs/latest/xml/reader/enum.XmlEvent.html
_ => {}
}
}
Ok(())
}
文档解析可以正常结束或以错误结束。无论具体原因如何,解析过程都会停止,迭代器会正常终止。
您还可以通过解析器的自己的next()
方法更细粒度地控制何时从解析器中拉取下一个事件
match parser.next() {
...
}
在文档结束时或发生错误时,解析器将记住最后一个事件,并在随后的next()
调用中始终返回它。如果使用迭代器,则它将产生错误或文档结束事件一次,之后将产生None
还可以使用xml::reader::ParserConfig
结构来稍微调整解析过程。请参阅其文档以获取更多信息示例。
解析不受信任的输入
解析器是用安全的Rust子集编写的,因此根据Rust的保证,它最坏的情况是导致panic。您可以使用ParserConfig
来设置名称、属性、文本、实体等最大长度的限制。
写入XML文档
xml-rs还提供了一个类似于StAX事件写入器的流式写入器。使用它,您可以将XML文档写入任何Write
实现者。
use std::io;
use xml::writer::{EmitterConfig, XmlEvent};
/// A simple demo syntax where "+foo" makes `<foo>`, "-foo" makes `</foo>`
fn make_event_from_line(line: &str) -> XmlEvent {
let line = line.trim();
if let Some(name) = line.strip_prefix("+") {
XmlEvent::start_element(name).into()
} else if line.starts_with("-") {
XmlEvent::end_element().into()
} else {
XmlEvent::characters(line).into()
}
}
fn main() -> io::Result<()> {
let input = io::stdin();
let out = io::stdout();
let mut writer = EmitterConfig::new()
.perform_indent(true)
.create_writer();
let mut line = String::new();
loop {
line.clear();
let bytes_read = input.read_line(&mut line)?;
if bytes_read == 0 {
break; // EOF
}
let event = make_event_from_line(&line);
if let Err(e) = writer.write(event) {
panic!("Write error: {e}")
}
}
out.write_all(writer.into_inner().as_bytes())
}
上述代码示例还演示了如何从其配置中创建写入器。同样的事情也适用于EventReader
。
该库提供了一个XML事件构建领域特定语言(DSL),它有助于构建复杂的事件,例如具有命名空间定义的事件。以下是一些示例
// <a:hello a:param="value" xmlns:a="urn:some:document">
XmlEvent::start_element("a:hello").attr("a:param", "value").ns("a", "urn:some:document")
// <hello b:config="name" xmlns="urn:default:uri">
XmlEvent::start_element("hello").attr("b:config", "value").default_ns("urn:defaul:uri")
// <![CDATA[some unescaped text]]>
XmlEvent::cdata("some unescaped text")
当然,可以直接创建 XmlEvent
枚举变体,而不是使用构建器 DSL。更多示例请参阅 xml::writer::XmlEvent
文档。
写入器有多个配置选项;有关更多信息,请参阅 EmitterConfig
文档。
错误报告
请将有关核心 XML 读取和写入的问题报告到: https://github.com/kornelski/xml-rs/issues。请将有关无标准分支的问题报告到: https://github.com/6d7a/xml-no-std/issues。