Skip to main content

easydoc_core/document/
document_block.rs

1use crate::{DocumentImage, DocumentList, DocumentTable, DocumentTextRun};
2
3/// 文档中的后端无关块级元素。
4///
5/// 对应 OOXML `<w:body>` 中的块级元素(段落/表格/列表/图片等)。
6/// 无直接 Java 对应(Java `EasyExcel` 不处理 DOCX),是 easydoc-rust 自创的语义模型。
7#[derive(Clone, Debug, PartialEq)]
8#[non_exhaustive]
9pub enum DocumentBlock {
10    /// 标题及其级别。
11    Heading {
12        /// 标题级别,范围一到六。
13        level: u8,
14        /// 标题富文本片段。
15        runs: Vec<DocumentTextRun>,
16    },
17    /// 普通段落。
18    Paragraph(Vec<DocumentTextRun>),
19    /// 表格。
20    Table(DocumentTable),
21    /// 列表。
22    List(DocumentList),
23    /// 图片。
24    Image(DocumentImage),
25    /// 水平分隔线。
26    ThematicBreak,
27    /// 强制分页。
28    PageBreak,
29    /// 强制分栏。
30    ColumnBreak,
31    /// 预格式化代码块。
32    CodeBlock {
33        /// 可选语言标记。
34        language: Option<String>,
35        /// 代码文本。
36        code: String,
37    },
38    /// 文本框中的块。
39    TextBox(Vec<DocumentBlock>),
40    /// 脚注。
41    Footnote {
42        /// 脚注标识。
43        id: u32,
44        /// 脚注内容。
45        blocks: Vec<DocumentBlock>,
46    },
47    /// 尾注。
48    Endnote {
49        /// 尾注标识。
50        id: u32,
51        /// 尾注内容。
52        blocks: Vec<DocumentBlock>,
53    },
54    /// 文档分区(Section),包含页面布局属性和子块。
55    Section {
56        /// 分区内的块级内容。
57        blocks: Vec<DocumentBlock>,
58        /// 可选的分区类型标识(如 nextPage, continuous 等)。
59        section_type: Option<String>,
60    },
61    /// 数学公式(OMML 原始 XML 或已转换的 LaTeX)。
62    Math {
63        /// OMML 原始 XML(`<m:oMath>...</m:oMath>`),或 `None` 表示已转换。
64        omml: Option<String>,
65        /// 已转换的 LaTeX(行内 `$...$` 用),或 `None` 表示需转换。
66        latex: Option<String>,
67        /// 是否为展示公式(block display,用 `$$...$$`)。
68        display: bool,
69    },
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn section_variant_roundtrip() {
78        let section = DocumentBlock::Section {
79            blocks: vec![DocumentBlock::Paragraph(vec![DocumentTextRun {
80                text: "hello".into(),
81                ..DocumentTextRun::default()
82            }])],
83            section_type: Some("nextPage".into()),
84        };
85        match &section {
86            DocumentBlock::Section {
87                blocks,
88                section_type,
89            } => {
90                assert_eq!(blocks.len(), 1);
91                assert_eq!(section_type.as_deref(), Some("nextPage"));
92            }
93            _ => panic!("expected Section"),
94        }
95    }
96
97    #[test]
98    fn math_variant_display_and_inline() {
99        let display = DocumentBlock::Math {
100            omml: None,
101            latex: Some(r"\frac{1}{2}".into()),
102            display: true,
103        };
104        let inline = DocumentBlock::Math {
105            omml: Some("<m:oMath><m:r><m:t>x</m:t></m:r></m:oMath>".into()),
106            latex: None,
107            display: false,
108        };
109        match &display {
110            DocumentBlock::Math { latex, display, .. } => {
111                assert_eq!(latex.as_deref(), Some(r"\frac{1}{2}"));
112                assert!(*display);
113            }
114            _ => panic!("expected Math"),
115        }
116        match &inline {
117            DocumentBlock::Math { omml, display, .. } => {
118                assert!(omml.is_some());
119                assert!(!(*display));
120            }
121            _ => panic!("expected Math"),
122        }
123    }
124
125    #[test]
126    fn document_block_is_non_exhaustive() {
127        // Verify _ => wildcard works for forward compat
128        let block = DocumentBlock::ThematicBreak;
129        let desc = match block {
130            DocumentBlock::Heading { .. } => "heading",
131            DocumentBlock::Paragraph(_) => "paragraph",
132            DocumentBlock::Table(_) => "table",
133            DocumentBlock::List(_) => "list",
134            DocumentBlock::Image(_) => "image",
135            DocumentBlock::ThematicBreak => "break",
136            DocumentBlock::PageBreak => "page",
137            DocumentBlock::ColumnBreak => "column",
138            DocumentBlock::CodeBlock { .. } => "code",
139            DocumentBlock::TextBox(_) => "textbox",
140            DocumentBlock::Footnote { .. } => "footnote",
141            DocumentBlock::Endnote { .. } => "endnote",
142            DocumentBlock::Section { .. } => "section",
143            DocumentBlock::Math { .. } => "math",
144        };
145        assert_eq!(desc, "break");
146    }
147}