Skip to main content

easyofd_core/doc/
ct_doc_info.rs

1//! 文档元数据信息(CT_DocInfo)。
2//!
3//! 对应 Java: org.ofdrw.core.basicStructure.ofd.docInfo.CT_DocInfo
4//!
5//! 描述 OFD 文档的标题、作者、创建日期、修改日期、DocID 等元数据。
6
7use std::fmt::Write;
8
9/// 文档用途枚举。
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum DocUsage {
12    /// 普通文档(默认值)。
13    Normal,
14    /// 电子书。
15    EBook,
16    /// 电子报纸。
17    ENewsPaper,
18    /// 电子期刊。
19    EMagazine,
20}
21
22impl DocUsage {
23    /// 获取枚举的字符串表示。
24    #[must_use]
25    pub fn as_str(&self) -> &'static str {
26        match self {
27            Self::Normal => "Normal",
28            Self::EBook => "EBook",
29            Self::ENewsPaper => "ENewsPaper",
30            Self::EMagazine => "EMagazine",
31        }
32    }
33
34    /// 从字符串解析。
35    #[allow(clippy::should_implement_trait)]
36    pub fn from_str(s: &str) -> Result<Self, String> {
37        match s {
38            "Normal" => Ok(Self::Normal),
39            "EBook" => Ok(Self::EBook),
40            "ENewsPaper" => Ok(Self::ENewsPaper),
41            "EMagazine" => Ok(Self::EMagazine),
42            _ => Err(format!("未知的文档用途: {s}")),
43        }
44    }
45}
46
47impl std::fmt::Display for DocUsage {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.write_str(self.as_str())
50    }
51}
52
53/// 文档元数据信息。
54///
55/// 描述文档的标识符、标题、作者、创建日期、修改日期等信息。
56#[derive(Debug, Clone)]
57pub struct CtDocInfo {
58    /// 文件标识符,UUID 格式(必选)。每个 DocID 在文件创建时分配。
59    pub doc_id: String,
60    /// 文档标题(可选),标题可以与文件名不同。
61    pub title: Option<String>,
62    /// 文档作者(可选)。
63    pub author: Option<String>,
64    /// 文档主题(可选)。
65    pub subject: Option<String>,
66    /// 文档摘要与注释(可选)。
67    pub abstract_text: Option<String>,
68    /// 文件创建日期(可选),格式 `YYYY-MM-DD`。
69    pub creation_date: Option<String>,
70    /// 文档最近修改日期(可选),格式 `YYYY-MM-DD`。
71    pub mod_date: Option<String>,
72    /// 文档分类(可选),默认 Normal。
73    pub doc_usage: Option<DocUsage>,
74    /// 文档封面路径(可选),指向一个图片文件。
75    pub cover: Option<String>,
76    /// 关键词列表(可选)。
77    pub keywords: Vec<String>,
78    /// 创建文档的应用程序(可选)。
79    pub creator: Option<String>,
80    /// 创建文档的应用程序版本(可选)。
81    pub creator_version: Option<String>,
82}
83
84impl CtDocInfo {
85    /// 创建新的文档元数据信息。
86    #[must_use]
87    pub fn new(doc_id: impl Into<String>) -> Self {
88        Self {
89            doc_id: doc_id.into(),
90            title: None,
91            author: None,
92            subject: None,
93            abstract_text: None,
94            creation_date: None,
95            mod_date: None,
96            doc_usage: None,
97            cover: None,
98            keywords: Vec::new(),
99            creator: None,
100            creator_version: None,
101        }
102    }
103
104    /// 设置文档标题。
105    #[must_use]
106    pub fn title(mut self, title: impl Into<String>) -> Self {
107        self.title = Some(title.into());
108        self
109    }
110
111    /// 设置文档作者。
112    #[must_use]
113    pub fn author(mut self, author: impl Into<String>) -> Self {
114        self.author = Some(author.into());
115        self
116    }
117
118    /// 设置文档主题。
119    #[must_use]
120    pub fn subject(mut self, subject: impl Into<String>) -> Self {
121        self.subject = Some(subject.into());
122        self
123    }
124
125    /// 设置文档摘要。
126    #[must_use]
127    pub fn abstract_text(mut self, text: impl Into<String>) -> Self {
128        self.abstract_text = Some(text.into());
129        self
130    }
131
132    /// 设置创建日期。
133    #[must_use]
134    pub fn creation_date(mut self, date: impl Into<String>) -> Self {
135        self.creation_date = Some(date.into());
136        self
137    }
138
139    /// 设置修改日期。
140    #[must_use]
141    pub fn mod_date(mut self, date: impl Into<String>) -> Self {
142        self.mod_date = Some(date.into());
143        self
144    }
145
146    /// 设置文档用途。
147    #[must_use]
148    pub fn doc_usage(mut self, usage: DocUsage) -> Self {
149        self.doc_usage = Some(usage);
150        self
151    }
152
153    /// 设置封面路径。
154    #[must_use]
155    pub fn cover(mut self, cover: impl Into<String>) -> Self {
156        self.cover = Some(cover.into());
157        self
158    }
159
160    /// 添加关键词。
161    #[must_use]
162    pub fn add_keyword(mut self, keyword: impl Into<String>) -> Self {
163        self.keywords.push(keyword.into());
164        self
165    }
166
167    /// 设置创建者应用程序。
168    #[must_use]
169    pub fn creator(mut self, creator: impl Into<String>) -> Self {
170        self.creator = Some(creator.into());
171        self
172    }
173
174    /// 设置创建者版本。
175    #[must_use]
176    pub fn creator_version(mut self, version: impl Into<String>) -> Self {
177        self.creator_version = Some(version.into());
178        self
179    }
180
181    /// 序列化为 XML 字符串。
182    #[must_use]
183    pub fn to_xml_string(&self) -> String {
184        let mut xml = String::from("<ofd:DocInfo>");
185
186        let _ = write!(xml, "\n<ofd:DocID>{}</ofd:DocID>", self.doc_id);
187
188        if let Some(ref title) = self.title {
189            let _ = write!(xml, "\n<ofd:Title>{title}</ofd:Title>");
190        }
191        if let Some(ref author) = self.author {
192            let _ = write!(xml, "\n<ofd:Author>{author}</ofd:Author>");
193        }
194        if let Some(ref subject) = self.subject {
195            let _ = write!(xml, "\n<ofd:Subject>{subject}</ofd:Subject>");
196        }
197        if let Some(ref abs) = self.abstract_text {
198            let _ = write!(xml, "\n<ofd:Abstract>{abs}</ofd:Abstract>");
199        }
200        if let Some(ref date) = self.creation_date {
201            let _ = write!(xml, "\n<ofd:CreationDate>{date}</ofd:CreationDate>");
202        }
203        if let Some(ref date) = self.mod_date {
204            let _ = write!(xml, "\n<ofd:ModDate>{date}</ofd:ModDate>");
205        }
206        if let Some(ref usage) = self.doc_usage {
207            let _ = write!(xml, "\n<ofd:DocUsage>{}</ofd:DocUsage>", usage.as_str());
208        }
209        if let Some(ref cover) = self.cover {
210            let _ = write!(xml, "\n<ofd:Cover>{cover}</ofd:Cover>");
211        }
212        if !self.keywords.is_empty() {
213            xml.push_str("\n<ofd:Keywords>");
214            for kw in &self.keywords {
215                let _ = write!(xml, "\n<ofd:Keyword>{kw}</ofd:Keyword>");
216            }
217            xml.push_str("\n</ofd:Keywords>");
218        }
219        if let Some(ref c) = self.creator {
220            let _ = write!(xml, "\n<ofd:Creator>{c}</ofd:Creator>");
221        }
222        if let Some(ref v) = self.creator_version {
223            let _ = write!(xml, "\n<ofd:CreatorVersion>{v}</ofd:CreatorVersion>");
224        }
225
226        xml.push_str("\n</ofd:DocInfo>");
227        xml
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_ct_doc_info_new() {
237        let info = CtDocInfo::new("550e8400e29b41d4a716446655440000");
238        assert_eq!(info.doc_id, "550e8400e29b41d4a716446655440000");
239        assert!(info.title.is_none());
240        assert!(info.author.is_none());
241        assert!(info.keywords.is_empty());
242    }
243
244    #[test]
245    fn test_ct_doc_info_builder() {
246        let info = CtDocInfo::new("abc123")
247            .title("测试文档")
248            .author("张三")
249            .subject("测试主题")
250            .creation_date("2025-01-01")
251            .mod_date("2025-06-01")
252            .doc_usage(DocUsage::EBook)
253            .add_keyword("OFD")
254            .add_keyword("测试")
255            .creator("easyofd-rust")
256            .creator_version("1.0");
257        assert_eq!(info.title.as_deref(), Some("测试文档"));
258        assert_eq!(info.author.as_deref(), Some("张三"));
259        assert_eq!(info.doc_usage, Some(DocUsage::EBook));
260        assert_eq!(info.keywords.len(), 2);
261    }
262
263    #[test]
264    fn test_ct_doc_info_xml() {
265        let info = CtDocInfo::new("doc001").title("My Doc").author("Author1");
266        let xml = info.to_xml_string();
267        assert!(xml.contains("<ofd:DocID>doc001</ofd:DocID>"));
268        assert!(xml.contains("<ofd:Title>My Doc</ofd:Title>"));
269        assert!(xml.contains("<ofd:Author>Author1</ofd:Author>"));
270        assert!(xml.contains("</ofd:DocInfo>"));
271    }
272
273    #[test]
274    fn test_doc_usage_display() {
275        assert_eq!(DocUsage::Normal.to_string(), "Normal");
276        assert_eq!(DocUsage::EBook.to_string(), "EBook");
277        assert_eq!(DocUsage::from_str("Normal").unwrap(), DocUsage::Normal);
278        assert!(DocUsage::from_str("Invalid").is_err());
279    }
280}