#xml-parser #xml-document #xml #parser #writer #sax

no-std xml-no-std

纯 Rust 编写的 XML 库

1 个不稳定版本

0.8.19 2024年2月13日

#2404解析器实现

Download history 1/week @ 2024-04-06 39/week @ 2024-04-27 7/week @ 2024-06-01 1/week @ 2024-06-08 39/week @ 2024-06-15 8/week @ 2024-06-22 7/week @ 2024-06-29 1/week @ 2024-07-06 20/week @ 2024-07-13 33/week @ 2024-07-20

61 每月下载量

MIT 许可证

255KB
4.5K SLoC

xml-no-std,是 xml-rs 的一个 no_std 分支

crates.io docs

文档

xml-no-std 是 Rust 编程语言的流行 XML 库 xml-rsno_std 分支。该库为了符合 no_std 规范,牺牲了流式处理能力和性能(仍然需要 alloc)。

所有功劳归于 netvlkornelski。感谢你们出色的工作 💚

动机

xml-no-std 的创建是为了支持 XML 编码规则,用于 librasn ASN.1 框架。由于 ASN.1 的各种编码规则,通常不会选择 XML 编码规则用于性能关键的应用场景。因此,性能损失是可以容忍的。

权衡

为了符合 no_std 环境,xml-no-std 使用 Iterator<Item = &u8> 进行读取和 alloc::string::String 进行写入,而不是 std::io::Readstd::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

无运行时依赖