#xml-parser #xml #parser #sax #xml-data #document

xml_oxide

符合W3C规范的任何有效XML的XML SAX解析器实现

12个不稳定版本 (3个重大变更)

0.3.0 2021年12月9日
0.2.0 2021年12月7日
0.1.1 2021年12月5日
0.0.9 2021年12月3日
0.0.4 2017年3月12日

2231解析器实现 中排名

Download history 65/week @ 2024-03-11 48/week @ 2024-03-18 71/week @ 2024-03-25 93/week @ 2024-04-01 37/week @ 2024-04-08 68/week @ 2024-04-15 84/week @ 2024-04-22 59/week @ 2024-04-29 46/week @ 2024-05-06 49/week @ 2024-05-13 79/week @ 2024-05-20 80/week @ 2024-05-27 78/week @ 2024-06-03 137/week @ 2024-06-10 108/week @ 2024-06-17 135/week @ 2024-06-24

每月下载量 466

MIT 许可证

115KB
3K SLoC

xml_oxide

crates.io github Released API docs

Rust XML解析器实现,以流式方式解析符合W3C规范的任何有效XML。

特性

  • 它使用类似常量的内存来处理大型XML文件
  • 足够快,适用于大多数用例。它可以在大约23秒内解析1GB的XML文件(内存中)
  • 支持XML 1.0中的命名空间
  • 它仅支持UTF-8编码
  • 它是一个非验证解析器,执行重要的有效性检查
  • 目前,它忽略处理指令、DTD/DOCTYPE中的有效性,并将它们作为原始字符串解析。它检查包括这些实体在内的总体有效性。(它甚至解析DOCTYPE中的注释以实现这一点)
  • 它可以解析非有效文档(请报告为错误)
  • 较大的实体以块的形式解析以保持内存使用量低:字符数据、CDATA部分、注释、空白
  • 当前,块大小默认为8KB,不可配置。内部缓冲区为16KB。如果您有一个大于缓冲区的元素标签或DOCTYPE声明,它可以回溯并分配更多内存以进行解析操作。测试使用1字节块大小。

不安全的使用

  • unsafe用于函数std::str::from_utf8_unchecked。它在已经使用std::str::from_utf8检查为有效UTF8字符串的字节切片上使用。虽然未测试性能提升。
  • 不再使用RefCell。有趣的是,只需将RefCell<Vec<u8>>更改为circular::Buffer即可通过Rust借用检查。我将保留此注释作为参考。RefCell的使用是因为Rust对在条件循环中使用可变项过于严格。希望非词法生命周期会随着时间的推移变得更好。

待办事项

  • 由于命名空间规范对名称中使用":"施加了约束,因此提供了namespace-aware=false选项来解析其他有效的XML 1.0文档。
  • 更多测试
  • 解析每个实体(包括DTD),以便能够利用合规性测试套件。

示例用法

在此示例中,统计了StartElementEndElement事件。注意,您可以在tests目录下找到更多示例。

  • StartElement还包括空标签。通过is_empty进行检查。
  • &amp;&#60;这样的引用实体在自己的事件中出现(不在Characters中)。
  • 解析字符/数值和预定义实体引用。自定义实体定义以原始形式传递。
  • 查看sax::Event以了解所有可用事件类型
use std::fs::File;
use xml_oxide::{sax::parser::Parser, sax::Event};


fn main() {
    println!("Starting...");

    let mut counter: usize = 0;
    let mut end_counter: usize = 0;

    let now = std::time::Instant::now();

    let f = File::open("./tests/xml_files/books.xml").unwrap();

    let mut p = Parser::from_reader(f);

    loop {
        let res = p.read_event();

        match res {
            Ok(event) => match event {
                Event::StartDocument => {}
                Event::EndDocument => {
                    break;
                }
                Event::StartElement(el) => {
                    //You can differantiate between Starting Tag and Empty Element Tag
                    if !el.is_empty {
                        counter = counter + 1;
                        // print every 10000th element name
                        if counter % 10000 == 0 {
                            println!("%10000 start {}", el.name);
                        }
                    }
                }
                Event::EndElement(el) => {
                    end_counter += 1;
                    if el.name == "feed" {
                        break;
                    }
                }
                Event::Characters(_) => {}
                Event::Reference(_) => {}
                _ => {}
            },
            Err(err) => {
                println!("{}", err);
                break;
            }
        }
    }

    println!("Start event count:{}", counter);
    println!("End event count:{}", end_counter);

    let elapsed = now.elapsed();
    println!("Time elapsed: {:.2?}", elapsed);
}


历史 & 信用

我试图在2017年指定一个类似于Java SAX库的推送解析器接口并实现它。这个想法是提供一个可以由社区中的多个实现使用的接口。它工作着(尽管速度较慢),但主要问题是推送解析器在Rust中不便于使用。经过长时间的思考和对Rust的进一步了解,我决定实现一个拉取解析器。目前,SAX(拉取)接口只是一个enum及其行为(如每个调用分割字符的可能性)。

如果您想使用xml_sax接口实现另一个解析器,我们可以讨论改进接口。目前,它已集成到这个crate中。

pulldown-cmark中的Why a pull parser?部分是一个很好的解释。

当前接口受到了quick-xmlxml-rs和Java库的启发。

nom是一个很棒的库。它只是你最初尝试时可能会做的事情的结晶和更好的版本(我知道)。它还展示了Rust中组合性的力量。

依赖关系

~1.2–1.9MB
~38K SLoC