1use crate::oxml::ns::{NS_DRAWING_MAIN, NS_DRAWING_RELS, NS_PRESENTATION_MAIN};
38use crate::oxml::writer::XmlWriter;
39
40#[derive(Clone, Debug, Default, PartialEq, Eq)]
49pub struct Comment {
50 pub author_id: u32,
52 pub date_time: String,
54 pub idx: u32,
56 pub pos_x: i64,
58 pub pos_y: i64,
60 pub text: String,
62}
63
64impl Comment {
65 pub fn new(author_id: u32, idx: u32, pos_x: i64, pos_y: i64, text: impl Into<String>) -> Self {
73 Comment {
74 author_id,
75 date_time: String::new(),
76 idx,
77 pos_x,
78 pos_y,
79 text: text.into(),
80 }
81 }
82
83 pub fn write_xml(&self, w: &mut XmlWriter) {
94 w.open_with(
95 "p:cm",
96 &[
97 ("authorId", &self.author_id.to_string()),
98 ("dt", &self.date_time),
99 ("idx", &self.idx.to_string()),
100 ],
101 );
102 w.empty_with(
103 "p:pos",
104 &[
105 ("x", &self.pos_x.to_string()),
106 ("y", &self.pos_y.to_string()),
107 ],
108 );
109 w.open("p:text");
110 w.text(&self.text);
111 w.close("p:text");
112 w.close("p:cm");
113 }
114}
115
116#[derive(Clone, Debug, Default, PartialEq, Eq)]
118pub struct CommentList {
119 pub comments: Vec<Comment>,
121}
122
123impl CommentList {
124 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn is_empty(&self) -> bool {
131 self.comments.is_empty()
132 }
133
134 pub fn len(&self) -> usize {
136 self.comments.len()
137 }
138
139 pub fn push(&mut self, c: Comment) {
141 self.comments.push(c);
142 }
143
144 pub fn to_xml(&self) -> String {
155 let mut w = XmlWriter::with_decl();
156 let attrs: Vec<(&str, &str)> = vec![
157 ("xmlns:a", NS_DRAWING_MAIN),
158 ("xmlns:p", NS_PRESENTATION_MAIN),
159 ("xmlns:r", NS_DRAWING_RELS),
160 ];
161 w.open_with("p:cmLst", &attrs);
162 for c in &self.comments {
163 c.write_xml(&mut w);
164 }
165 w.close("p:cmLst");
166 w.into_string()
167 }
168}
169
170#[derive(Clone, Debug, Default, PartialEq, Eq)]
172pub struct CommentAuthor {
173 pub id: u32,
175 pub name: String,
177 pub initials: String,
179}
180
181impl CommentAuthor {
182 pub fn new(id: u32, name: impl Into<String>, initials: impl Into<String>) -> Self {
184 CommentAuthor {
185 id,
186 name: name.into(),
187 initials: initials.into(),
188 }
189 }
190
191 pub fn write_xml(&self, w: &mut XmlWriter) {
193 w.empty_with(
194 "p:cmAuthor",
195 &[
196 ("id", &self.id.to_string()),
197 ("name", &self.name),
198 ("initials", &self.initials),
199 ],
200 );
201 }
202}
203
204#[derive(Clone, Debug, Default, PartialEq, Eq)]
206pub struct CommentAuthorList {
207 pub authors: Vec<CommentAuthor>,
209}
210
211impl CommentAuthorList {
212 pub fn new() -> Self {
214 Self::default()
215 }
216
217 pub fn is_empty(&self) -> bool {
219 self.authors.is_empty()
220 }
221
222 pub fn len(&self) -> usize {
224 self.authors.len()
225 }
226
227 pub fn push(&mut self, a: CommentAuthor) {
229 self.authors.push(a);
230 }
231
232 pub fn get_by_id(&self, id: u32) -> Option<&CommentAuthor> {
234 self.authors.iter().find(|a| a.id == id)
235 }
236
237 pub fn get_or_insert_id(&mut self, name: &str, initials: &str) -> u32 {
241 if let Some(a) = self.authors.iter().find(|a| a.name == name) {
242 return a.id;
243 }
244 let next_id = self
245 .authors
246 .iter()
247 .map(|a| a.id)
248 .max()
249 .unwrap_or(0)
250 .saturating_add(1);
251 self.authors
252 .push(CommentAuthor::new(next_id, name, initials));
253 next_id
254 }
255
256 pub fn to_xml(&self) -> String {
258 let mut w = XmlWriter::with_decl();
259 let attrs: Vec<(&str, &str)> = vec![("xmlns:p", NS_PRESENTATION_MAIN)];
260 w.open_with("p:cmAuthorLst", &attrs);
261 for a in &self.authors {
262 a.write_xml(&mut w);
263 }
264 w.close("p:cmAuthorLst");
265 w.into_string()
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn comment_write_xml_basic() {
275 let c = Comment::new(0, 1, 100, 200, "Hello");
276 let mut w = XmlWriter::new();
277 c.write_xml(&mut w);
278 let xml = &w.buf;
279 assert!(xml.contains("<p:cm authorId=\"0\""));
280 assert!(xml.contains("idx=\"1\""));
281 assert!(xml.contains("<p:pos x=\"100\" y=\"200\"/>"));
282 assert!(xml.contains("<p:text>Hello</p:text>"));
283 }
284
285 #[test]
286 fn comment_list_to_xml() {
287 let mut lst = CommentList::new();
288 lst.push(Comment::new(0, 1, 100, 200, "First"));
289 lst.push(Comment::new(1, 2, 300, 400, "Second"));
290 let xml = lst.to_xml();
291 assert!(xml.contains("<?xml"));
292 assert!(xml.contains("<p:cmLst"));
293 assert!(xml.contains("xmlns:a="));
294 assert!(xml.contains("xmlns:p="));
295 assert!(xml.contains("First"));
296 assert!(xml.contains("Second"));
297 }
298
299 #[test]
300 fn comment_list_empty_to_xml() {
301 let lst = CommentList::new();
302 let xml = lst.to_xml();
303 assert!(xml.contains("<p:cmLst"));
304 assert!(xml.contains("</p:cmLst>"));
305 assert!(!xml.contains("<p:cm "));
307 }
308
309 #[test]
310 fn comment_author_write_xml() {
311 let a = CommentAuthor::new(0, "张三", "ZS");
312 let mut w = XmlWriter::new();
313 a.write_xml(&mut w);
314 let xml = &w.buf;
315 assert!(xml.contains("<p:cmAuthor id=\"0\""));
316 assert!(xml.contains("name=\"张三\""));
317 assert!(xml.contains("initials=\"ZS\""));
318 }
319
320 #[test]
321 fn comment_author_list_to_xml() {
322 let mut lst = CommentAuthorList::new();
323 lst.push(CommentAuthor::new(0, "张三", "ZS"));
324 lst.push(CommentAuthor::new(1, "李四", "LS"));
325 let xml = lst.to_xml();
326 assert!(xml.contains("<?xml"));
327 assert!(xml.contains("<p:cmAuthorLst"));
328 assert!(xml.contains("张三"));
329 assert!(xml.contains("李四"));
330 }
331
332 #[test]
333 fn comment_author_get_or_insert() {
334 let mut lst = CommentAuthorList::new();
335 let id1 = lst.get_or_insert_id("张三", "ZS");
336 assert_eq!(id1, 1);
337 let id2 = lst.get_or_insert_id("李四", "LS");
338 assert_eq!(id2, 2);
339 let id3 = lst.get_or_insert_id("张三", "ZS");
341 assert_eq!(id3, 1);
342 assert_eq!(lst.len(), 2);
343 }
344
345 #[test]
346 fn comment_author_get_by_id() {
347 let mut lst = CommentAuthorList::new();
348 lst.push(CommentAuthor::new(0, "张三", "ZS"));
349 lst.push(CommentAuthor::new(1, "李四", "LS"));
350 assert_eq!(lst.get_by_id(0).unwrap().name, "张三");
351 assert_eq!(lst.get_by_id(1).unwrap().name, "李四");
352 assert!(lst.get_by_id(2).is_none());
353 }
354}