oxml_core/
core_properties.rs1use quick_xml::Reader;
4use quick_xml::events::Event;
5
6use crate::error::Result;
7use crate::xml::local_name;
8
9#[derive(Debug, Clone, Default, PartialEq)]
11pub struct CoreProperties {
12 pub title: Option<String>,
14 pub creator: Option<String>,
16 pub subject: Option<String>,
18 pub description: Option<String>,
20 pub keywords: Option<String>,
22 pub last_modified_by: Option<String>,
24 pub created: Option<String>,
26 pub modified: Option<String>,
28}
29
30impl CoreProperties {
31 pub fn from_xml(xml: &[u8]) -> Result<Self> {
33 let mut reader = Reader::from_reader(xml);
34 reader.config_mut().trim_text(true);
35
36 let mut props = CoreProperties::default();
37 let mut buf = Vec::new();
38
39 loop {
40 match reader.read_event_into(&mut buf) {
41 Ok(Event::Start(ref e)) => {
42 let name = e.name();
43 let field = match local_name(name.as_ref()) {
44 b"title" => &mut props.title,
45 b"creator" => &mut props.creator,
46 b"subject" => &mut props.subject,
47 b"description" => &mut props.description,
48 b"keywords" => &mut props.keywords,
49 b"lastModifiedBy" => &mut props.last_modified_by,
50 b"created" => &mut props.created,
51 b"modified" => &mut props.modified,
52 _ => {
53 buf.clear();
54 continue;
55 }
56 };
57 let text = crate::xml_text::read_element_text(&mut reader, name);
61 if !text.is_empty() {
62 *field = Some(text);
63 }
64 }
65 Ok(Event::Eof) => break,
66 Err(e) => return Err(e.into()),
67 _ => {}
68 }
69 buf.clear();
70 }
71
72 Ok(props)
73 }
74
75 pub fn to_xml(&self) -> Result<Vec<u8>> {
77 use quick_xml::Writer;
78 use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText};
79
80 let mut writer = Writer::new_with_indent(Vec::new(), b' ', 2);
81
82 writer.write_event(Event::Decl(BytesDecl::new(
83 "1.0",
84 Some("UTF-8"),
85 Some("yes"),
86 )))?;
87
88 let mut root = BytesStart::new("cp:coreProperties");
89 root.push_attribute((
90 "xmlns:cp",
91 "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
92 ));
93 root.push_attribute(("xmlns:dc", "http://purl.org/dc/elements/1.1/"));
94 root.push_attribute(("xmlns:dcterms", "http://purl.org/dc/terms/"));
95 root.push_attribute(("xmlns:dcmitype", "http://purl.org/dc/dcmitype/"));
96 root.push_attribute(("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"));
97 writer.write_event(Event::Start(root))?;
98
99 fn write_element<W: std::io::Write>(
100 writer: &mut Writer<W>,
101 tag: &str,
102 value: &Option<String>,
103 ) -> Result<()> {
104 if let Some(val) = value {
105 writer.write_event(Event::Start(BytesStart::new(tag)))?;
106 writer.write_event(Event::Text(BytesText::new(val)))?;
107 writer.write_event(Event::End(BytesEnd::new(tag)))?;
108 }
109 Ok(())
110 }
111
112 fn write_date_element<W: std::io::Write>(
113 writer: &mut Writer<W>,
114 tag: &str,
115 value: &Option<String>,
116 ) -> Result<()> {
117 if let Some(val) = value {
118 let mut e = BytesStart::new(tag);
119 e.push_attribute(("xsi:type", "dcterms:W3CDTF"));
120 writer.write_event(Event::Start(e))?;
121 writer.write_event(Event::Text(BytesText::new(val)))?;
122 writer.write_event(Event::End(BytesEnd::new(tag)))?;
123 }
124 Ok(())
125 }
126
127 write_element(&mut writer, "dc:title", &self.title)?;
128 write_element(&mut writer, "dc:subject", &self.subject)?;
129 write_element(&mut writer, "dc:creator", &self.creator)?;
130 write_element(&mut writer, "cp:keywords", &self.keywords)?;
131 write_element(&mut writer, "dc:description", &self.description)?;
132 write_element(&mut writer, "cp:lastModifiedBy", &self.last_modified_by)?;
133 write_date_element(&mut writer, "dcterms:created", &self.created)?;
134 write_date_element(&mut writer, "dcterms:modified", &self.modified)?;
135
136 writer.write_event(Event::End(BytesEnd::new("cp:coreProperties")))?;
137
138 Ok(writer.into_inner())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn parse_core_properties() {
148 let xml = br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
149<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
150 xmlns:dc="http://purl.org/dc/elements/1.1/"
151 xmlns:dcterms="http://purl.org/dc/terms/"
152 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
153 <dc:title>Test Document</dc:title>
154 <dc:creator>John Doe</dc:creator>
155 <dc:subject>Testing</dc:subject>
156 <dc:description>A test document</dc:description>
157 <cp:keywords>test, document</cp:keywords>
158 <cp:lastModifiedBy>Jane Doe</cp:lastModifiedBy>
159 <dcterms:created xsi:type="dcterms:W3CDTF">2024-01-15T10:30:00Z</dcterms:created>
160 <dcterms:modified xsi:type="dcterms:W3CDTF">2024-06-20T14:00:00Z</dcterms:modified>
161</cp:coreProperties>"#;
162
163 let props = CoreProperties::from_xml(xml).unwrap();
164 assert_eq!(props.title, Some("Test Document".to_string()));
165 assert_eq!(props.creator, Some("John Doe".to_string()));
166 assert_eq!(props.subject, Some("Testing".to_string()));
167 assert_eq!(props.description, Some("A test document".to_string()));
168 assert_eq!(props.keywords, Some("test, document".to_string()));
169 assert_eq!(props.last_modified_by, Some("Jane Doe".to_string()));
170 assert_eq!(props.created, Some("2024-01-15T10:30:00Z".to_string()));
171 assert_eq!(props.modified, Some("2024-06-20T14:00:00Z".to_string()));
172 }
173
174 #[test]
175 fn round_trip_core_properties() {
176 let props = CoreProperties {
177 title: Some("My Title".to_string()),
178 creator: Some("Author".to_string()),
179 subject: None,
180 description: None,
181 keywords: Some("rust, docx".to_string()),
182 last_modified_by: None,
183 created: Some("2024-01-01T00:00:00Z".to_string()),
184 modified: Some("2024-06-01T00:00:00Z".to_string()),
185 };
186
187 let xml = props.to_xml().unwrap();
188 let parsed = CoreProperties::from_xml(&xml).unwrap();
189
190 assert_eq!(parsed.title, props.title);
191 assert_eq!(parsed.creator, props.creator);
192 assert_eq!(parsed.keywords, props.keywords);
193 assert_eq!(parsed.created, props.created);
194 assert_eq!(parsed.modified, props.modified);
195 }
196}