1use sheetkit_xml::comments::{Authors, Comment, CommentList, CommentRun, CommentText, Comments};
6use sheetkit_xml::namespaces;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct CommentConfig {
11 pub cell: String,
13 pub author: String,
15 pub text: String,
17}
18
19pub fn add_comment(comments: &mut Option<Comments>, config: &CommentConfig) {
23 let c = comments.get_or_insert_with(|| Comments {
24 xmlns: namespaces::SPREADSHEET_ML.to_string(),
25 authors: Authors {
26 authors: Vec::new(),
27 },
28 comment_list: CommentList {
29 comments: Vec::new(),
30 },
31 });
32
33 let author_id = match c.authors.authors.iter().position(|a| a == &config.author) {
35 Some(idx) => idx as u32,
36 None => {
37 c.authors.authors.push(config.author.clone());
38 (c.authors.authors.len() - 1) as u32
39 }
40 };
41
42 c.comment_list
44 .comments
45 .retain(|comment| comment.r#ref != config.cell);
46
47 c.comment_list.comments.push(Comment {
49 r#ref: config.cell.clone(),
50 author_id,
51 text: CommentText {
52 runs: vec![CommentRun {
53 rpr: None,
54 t: config.text.clone(),
55 }],
56 },
57 });
58}
59
60pub fn get_comment(comments: &Option<Comments>, cell: &str) -> Option<CommentConfig> {
64 let c = comments.as_ref()?;
65 let comment = c.comment_list.comments.iter().find(|cm| cm.r#ref == cell)?;
66
67 let author = c
68 .authors
69 .authors
70 .get(comment.author_id as usize)
71 .cloned()
72 .unwrap_or_default();
73
74 let text = comment
75 .text
76 .runs
77 .iter()
78 .map(|r| r.t.as_str())
79 .collect::<Vec<_>>()
80 .join("");
81
82 Some(CommentConfig {
83 cell: cell.to_string(),
84 author,
85 text,
86 })
87}
88
89pub fn remove_comment(comments: &mut Option<Comments>, cell: &str) -> bool {
93 if let Some(ref mut c) = comments {
94 let before = c.comment_list.comments.len();
95 c.comment_list
96 .comments
97 .retain(|comment| comment.r#ref != cell);
98 let removed = c.comment_list.comments.len() < before;
99
100 if c.comment_list.comments.is_empty() {
102 *comments = None;
103 }
104
105 removed
106 } else {
107 false
108 }
109}
110
111pub fn get_all_comments(comments: &Option<Comments>) -> Vec<CommentConfig> {
113 match comments.as_ref() {
114 Some(c) => c
115 .comment_list
116 .comments
117 .iter()
118 .map(|comment| {
119 let author = c
120 .authors
121 .authors
122 .get(comment.author_id as usize)
123 .cloned()
124 .unwrap_or_default();
125 let text = comment
126 .text
127 .runs
128 .iter()
129 .map(|r| r.t.as_str())
130 .collect::<Vec<_>>()
131 .join("");
132 CommentConfig {
133 cell: comment.r#ref.clone(),
134 author,
135 text,
136 }
137 })
138 .collect(),
139 None => Vec::new(),
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_add_comment() {
149 let mut comments = None;
150 let config = CommentConfig {
151 cell: "A1".to_string(),
152 author: "Alice".to_string(),
153 text: "Hello comment".to_string(),
154 };
155 add_comment(&mut comments, &config);
156
157 assert!(comments.is_some());
158 let c = comments.as_ref().unwrap();
159 assert_eq!(c.authors.authors.len(), 1);
160 assert_eq!(c.authors.authors[0], "Alice");
161 assert_eq!(c.comment_list.comments.len(), 1);
162 assert_eq!(c.comment_list.comments[0].r#ref, "A1");
163 }
164
165 #[test]
166 fn test_get_comment() {
167 let mut comments = None;
168 let config = CommentConfig {
169 cell: "A1".to_string(),
170 author: "Alice".to_string(),
171 text: "Test comment".to_string(),
172 };
173 add_comment(&mut comments, &config);
174
175 let result = get_comment(&comments, "A1");
176 assert!(result.is_some());
177 let c = result.unwrap();
178 assert_eq!(c.cell, "A1");
179 assert_eq!(c.author, "Alice");
180 assert_eq!(c.text, "Test comment");
181 }
182
183 #[test]
184 fn test_get_comment_nonexistent() {
185 let comments: Option<Comments> = None;
186 assert!(get_comment(&comments, "A1").is_none());
187 }
188
189 #[test]
190 fn test_get_comment_wrong_cell() {
191 let mut comments = None;
192 let config = CommentConfig {
193 cell: "A1".to_string(),
194 author: "Alice".to_string(),
195 text: "Test".to_string(),
196 };
197 add_comment(&mut comments, &config);
198 assert!(get_comment(&comments, "B1").is_none());
199 }
200
201 #[test]
202 fn test_remove_comment() {
203 let mut comments = None;
204 let config = CommentConfig {
205 cell: "A1".to_string(),
206 author: "Alice".to_string(),
207 text: "Test".to_string(),
208 };
209 add_comment(&mut comments, &config);
210 assert!(remove_comment(&mut comments, "A1"));
211 assert!(comments.is_none());
212 }
213
214 #[test]
215 fn test_remove_nonexistent_comment() {
216 let mut comments: Option<Comments> = None;
217 assert!(!remove_comment(&mut comments, "A1"));
218 }
219
220 #[test]
221 fn test_multiple_comments_different_cells() {
222 let mut comments = None;
223 add_comment(
224 &mut comments,
225 &CommentConfig {
226 cell: "A1".to_string(),
227 author: "Alice".to_string(),
228 text: "Comment 1".to_string(),
229 },
230 );
231 add_comment(
232 &mut comments,
233 &CommentConfig {
234 cell: "B2".to_string(),
235 author: "Bob".to_string(),
236 text: "Comment 2".to_string(),
237 },
238 );
239 add_comment(
240 &mut comments,
241 &CommentConfig {
242 cell: "C3".to_string(),
243 author: "Alice".to_string(),
244 text: "Comment 3".to_string(),
245 },
246 );
247
248 let all = get_all_comments(&comments);
249 assert_eq!(all.len(), 3);
250
251 let c = comments.as_ref().unwrap();
253 assert_eq!(c.authors.authors.len(), 2); let c1 = get_comment(&comments, "A1").unwrap();
257 assert_eq!(c1.text, "Comment 1");
258 assert_eq!(c1.author, "Alice");
259
260 let c2 = get_comment(&comments, "B2").unwrap();
261 assert_eq!(c2.text, "Comment 2");
262 assert_eq!(c2.author, "Bob");
263 }
264
265 #[test]
266 fn test_overwrite_comment_on_same_cell() {
267 let mut comments = None;
268 add_comment(
269 &mut comments,
270 &CommentConfig {
271 cell: "A1".to_string(),
272 author: "Alice".to_string(),
273 text: "Original".to_string(),
274 },
275 );
276 add_comment(
277 &mut comments,
278 &CommentConfig {
279 cell: "A1".to_string(),
280 author: "Bob".to_string(),
281 text: "Updated".to_string(),
282 },
283 );
284
285 let all = get_all_comments(&comments);
286 assert_eq!(all.len(), 1);
287 assert_eq!(all[0].text, "Updated");
288 assert_eq!(all[0].author, "Bob");
289 }
290
291 #[test]
292 fn test_remove_one_of_multiple_comments() {
293 let mut comments = None;
294 add_comment(
295 &mut comments,
296 &CommentConfig {
297 cell: "A1".to_string(),
298 author: "Alice".to_string(),
299 text: "First".to_string(),
300 },
301 );
302 add_comment(
303 &mut comments,
304 &CommentConfig {
305 cell: "B2".to_string(),
306 author: "Bob".to_string(),
307 text: "Second".to_string(),
308 },
309 );
310
311 assert!(remove_comment(&mut comments, "A1"));
312 assert!(comments.is_some()); let all = get_all_comments(&comments);
315 assert_eq!(all.len(), 1);
316 assert_eq!(all[0].cell, "B2");
317 }
318
319 #[test]
320 fn test_get_all_comments_empty() {
321 let comments: Option<Comments> = None;
322 let all = get_all_comments(&comments);
323 assert!(all.is_empty());
324 }
325
326 #[test]
327 fn test_comments_xml_roundtrip() {
328 let mut comments = None;
329 add_comment(
330 &mut comments,
331 &CommentConfig {
332 cell: "A1".to_string(),
333 author: "Author".to_string(),
334 text: "A test comment".to_string(),
335 },
336 );
337
338 let c = comments.as_ref().unwrap();
339 let xml = quick_xml::se::to_string(c).unwrap();
340 let parsed: Comments = quick_xml::de::from_str(&xml).unwrap();
341
342 assert_eq!(parsed.authors.authors.len(), 1);
343 assert_eq!(parsed.comment_list.comments.len(), 1);
344 assert_eq!(parsed.comment_list.comments[0].r#ref, "A1");
345 assert_eq!(
346 parsed.comment_list.comments[0].text.runs[0].t,
347 "A test comment"
348 );
349 }
350}