Skip to main content

easyofd_core/attachment/
ct_attachment.rs

1//! 单个附件。
2
3use std::fmt::Write;
4
5/// 对应 Java: org.ofdrw.core.attachment.CT_Attachment
6///
7/// 表示一个单独的附件对象,包含附件的标识、名称、格式、
8/// 创建日期、大小、可见性和文件数据。
9#[derive(Debug, Clone, PartialEq)]
10pub struct CTAttachment {
11    /// 附件 ID。
12    pub id: String,
13    /// 附件名称。
14    pub name: String,
15    /// 附件格式(MIME 类型或文件扩展名)。
16    pub format: Option<String>,
17    /// 创建日期(ISO 8601 格式字符串)。
18    pub creation_date: Option<String>,
19    /// 附件大小(字节)。
20    pub size: Option<u64>,
21    /// 附件是否可见。
22    pub visible: bool,
23    /// 附件文件路径(OFD 包内相对路径)。
24    pub file: Option<String>,
25}
26
27impl CTAttachment {
28    /// 创建一个新的附件。
29    #[must_use]
30    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
31        Self {
32            id: id.into(),
33            name: name.into(),
34            format: None,
35            creation_date: None,
36            size: None,
37            visible: true,
38            file: None,
39        }
40    }
41
42    /// 设置格式。
43    #[must_use]
44    pub fn format(mut self, format: impl Into<String>) -> Self {
45        self.format = Some(format.into());
46        self
47    }
48
49    /// 设置创建日期。
50    #[must_use]
51    pub fn creation_date(mut self, date: impl Into<String>) -> Self {
52        self.creation_date = Some(date.into());
53        self
54    }
55
56    /// 设置大小(字节)。
57    #[must_use]
58    pub fn size(mut self, size: u64) -> Self {
59        self.size = Some(size);
60        self
61    }
62
63    /// 设置是否可见。
64    #[must_use]
65    pub fn visible(mut self, visible: bool) -> Self {
66        self.visible = visible;
67        self
68    }
69
70    /// 设置附件文件路径。
71    #[must_use]
72    pub fn file(mut self, file: impl Into<String>) -> Self {
73        self.file = Some(file.into());
74        self
75    }
76
77    /// 序列化为 XML 字符串。
78    #[must_use]
79    pub fn to_xml_string(&self) -> String {
80        let mut xml = format!(r#"<ofd:Attachment ID="{}" Name="{}""#, self.id, self.name);
81
82        if let Some(ref fmt) = self.format {
83            let _ = write!(xml, r#" Format="{fmt}""#);
84        }
85        if let Some(ref date) = self.creation_date {
86            let _ = write!(xml, r#" CreationDate="{date}""#);
87        }
88        if let Some(sz) = self.size {
89            let _ = write!(xml, r#" Size="{sz}""#);
90        }
91        if !self.visible {
92            xml.push_str(r#" Visible="false""#);
93        }
94        if let Some(ref file) = self.file {
95            let _ = write!(xml, r#" File="{file}""#);
96        }
97
98        xml.push_str(" />");
99        xml
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_ct_attachment_new() {
109        let a = CTAttachment::new("att1", "readme.pdf");
110        assert_eq!(a.id, "att1");
111        assert_eq!(a.name, "readme.pdf");
112        assert!(a.format.is_none());
113        assert!(a.creation_date.is_none());
114        assert!(a.size.is_none());
115        assert!(a.visible);
116        assert!(a.file.is_none());
117    }
118
119    #[test]
120    fn test_ct_attachment_builder() {
121        let a = CTAttachment::new("att2", "data.xlsx")
122            .format("application/vnd.ms-excel")
123            .creation_date("2025-03-15")
124            .size(1024)
125            .visible(false)
126            .file("Attachments/data.xlsx");
127        assert_eq!(a.format.as_deref(), Some("application/vnd.ms-excel"));
128        assert_eq!(a.creation_date.as_deref(), Some("2025-03-15"));
129        assert_eq!(a.size, Some(1024));
130        assert!(!a.visible);
131        assert_eq!(a.file.as_deref(), Some("Attachments/data.xlsx"));
132    }
133
134    #[test]
135    fn test_ct_attachment_to_xml_string_basic() {
136        let a = CTAttachment::new("a1", "test.txt");
137        let xml = a.to_xml_string();
138        assert!(xml.contains(r#"ID="a1""#));
139        assert!(xml.contains(r#"Name="test.txt""#));
140        assert!(xml.contains(" />"));
141    }
142
143    #[test]
144    fn test_ct_attachment_to_xml_string_full() {
145        let a = CTAttachment::new("a2", "img.png")
146            .format("image/png")
147            .creation_date("2025-06-01")
148            .size(2048)
149            .visible(false)
150            .file("Attachments/img.png");
151        let xml = a.to_xml_string();
152        assert!(xml.contains(r#"Format="image/png""#));
153        assert!(xml.contains(r#"CreationDate="2025-06-01""#));
154        assert!(xml.contains(r#"Size="2048""#));
155        assert!(xml.contains(r#"Visible="false""#));
156        assert!(xml.contains(r#"File="Attachments/img.png""#));
157    }
158
159    #[test]
160    fn test_ct_attachment_visible_default_true() {
161        let a = CTAttachment::new("a1", "x.txt");
162        let xml = a.to_xml_string();
163        assert!(!xml.contains("Visible"));
164    }
165
166    #[test]
167    fn test_ct_attachment_clone_debug() {
168        let a = CTAttachment::new("x", "y.txt");
169        let a2 = a.clone();
170        assert_eq!(a2.id, "x");
171        assert!(format!("{a:?}").contains("CTAttachment"));
172    }
173}