15 个版本
| 0.3.0 | 2024 年 4 月 28 日 | 
|---|---|
| 0.2.2 | 2024 年 4 月 13 日 | 
| 0.2.0 | 2024 年 3 月 12 日 | 
| 0.1.10 | 2024 年 3 月 11 日 | 
| 0.1.1 | 2023 年 10 月 29 日 | 
#445 在 解析器实现
211 每月下载量
用于 shiva
74KB
 1.5K  SLoC
rtf-parser
一个安全的 Rust RTF 解析器和词法分析器库,旨在速度和内存效率,且无外部依赖。
它实现了 RTF 规范的最新版本(1.9),并支持现代 UTF-16 Unicode。
官方文档可在 docs.rs/rtf-parser 查找。
安装
可以使用 cargo 命令行工具安装此库
 cargo add rtf-parser
或者在您的 Cargo.toml 中的 [dependencies] 下添加 rtf-parser = "<last-version>"。
设计
该库分为 2 个主要组件
- 词法分析器
- 解析器
词法分析器扫描文档,并返回一个 Vec<Token>,以代码可理解的方式表示 RTF 文件。然后将这些标记传递给解析器,将其转换为真实文档:RtfDocument。
use rtf_parser::lexer::Lexer;
use rtf_parser::tokens::Token;
use rtf_parser::parser::Parser;
use rtf_parser::document::RtfDocument;
fn main() -> Result<(), Box<dyn Error>> {
    let tokens: Vec<Token> = Lexer::scan("<rtf>")?;
    let parser = Parser::new(tokens);
    let doc: RtfDocument = parser.parse()?;    
}
或者更简洁的方式
use rtf_parser::document::RtfDocument;
fn main() -> Result<(), Box<dyn Error>> {
    let doc: RtfDocument = RtfDocument::try_from("<rtf>")?;    
}
RtfDocument 结构实现了 TryFrom 特性,以
- &str
- String
- &mut std::fs::File
和内部处理输入输出的 from_filepath 构造函数。
返回的错误可以是 LexerError 或 ParserError,具体取决于失败的阶段。
RtfDocument 由以下部分组成
- 包含字体表、颜色表和编码等内容的 头信息。
- 主体,它是一个 Vec<StyledBlock>。
一个 StyledBlock 包含了特定文本块的所有格式信息。
它包含一个用于文本样式的 Painter,一个用于布局的 Paragraph,以及文本(String)。下面的代码定义了 Painter,渲染实现由用户决定。
pub struct Painter {
    pub font_ref: FontRef,
    pub font_size: u16,
    pub bold: bool,
    pub italic: bool,
    pub underline: bool,
    pub superscript: bool,
    pub subscript: bool,
    pub smallcaps: bool,
    pub strike: bool,
}
布局信息通过 paragraph 属性公开。
pub struct Paragraph {
    pub alignment: Alignment,
    pub spacing: Spacing,
    pub indent: Indentation,
    pub tab_width: i32,
}
它定义了块的对齐方式,使用的间距等...
您还可以使用 RtfDocument 结构体的 to_text 方法提取不带任何格式信息的文本。
fn main() -> Result<(), Box<dyn Error>> {
    let rtf = r#"{\rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard Voici du texte en {\b gras}.\par}"#;
    let tokens = Lexer::scan(rtf)?;
    let document = Parser::new(tokens)?;
    let text = document.to_text();
    assert_eq!(text, "Voici du texte en gras.");
}
示例
以下是一个完整的 rtf 解析示例。
use rtf_parser::lexer::Lexer;
use rtf_parser::parser::Parser;
fn main() -> Result<(), Box<dyn Error>> {
    let rtf_text = r#"{ \rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard Voici du texte en {\b gras}.\par }"#;
    let tokens = Lexer::scan(rtf_text)?;
    let doc = Parser::new(tokens).parse()?;
    assert_eq!(
        doc.header,
        RtfHeader {
            character_set: Ansi,
            color_table: ColorTable::Default(),
            font_table: FontTable::from([
                (0, Font { name: "Helvetica", character_set: 0, font_family: Swiss })
            ])
        }
    );
    assert_eq!(
        doc.body,
        [
            StyleBlock {
                painter: Painter { font_ref: 0, font_size: 0, bold: false, italic: false, underline: false },
                paragraph: Paragraph {
                    alignment: LeftAligned,
                    spacing: Spacing { before: 0, after: 0, between_line: Auto, line_multiplier: 0, },
                    indent: Indentation { left: 0, right: 0, first_line: 0, },
                    tab_width: 0,
                },
                text: "Voici du texte en ",
            },
            StyleBlock {
                painter: Painter { font_ref: 0, font_size: 0, bold: true, italic: false, underline: false },
                paragraph: Paragraph {
                    alignment: LeftAligned,
                    spacing: Spacing { before: 0, after: 0, between_line: Auto, line_multiplier: 0, },
                    indent: Indentation { left: 0, right: 0, first_line: 0, },
                    tab_width: 0,
                },
                text: "gras",
            },
            StyleBlock {
                painter: Painter { font_ref: 0, font_size: 0, bold: false, italic: false, underline: false },
                paragraph: Paragraph {
                    alignment: LeftAligned,
                    spacing: Spacing { before: 0, after: 0, between_line: Auto, line_multiplier: 0, },
                    indent: Indentation { left: 0, right: 0, first_line: 0, },
                    tab_width: 0,
                },
                text: ".",
            },
        ]
    );
    return Ok(());
}
已知限制
目前,\bin 关键字没有被考虑。因为其内容是二进制格式的文本,它可能会影响词法分析算法,并导致程序崩溃。未来将很快支持二进制。
同样不支持 base64 图片,但可以安全地解析。
基准测试
目前没有与 rtf-parser 相比较的 crates。
然而,rtf-grimoire crate 提供了一个相似的 Lexer。以下是针对一个 500kB rtf 文档 的词法分析和解析的快速基准测试。
| crates | 版本 | 持续时间 | 
|---|---|---|
| rtf-parser | v0.3.0 | 7 ms | 
| rtf-grimoire(仅词法分析) | v0.2.1 | 13 ms | 
此基准测试在一个 Intel MacBook Pro 上以发布版运行.
对于 rtf-parser,大部分的计算时间(65%)都花在词法分析过程中。仍有很大的改进空间。
依赖关系
~1.5MB
~37K SLoC