1use std::io::Write;
7
8use crate::node::NodeRef;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum EditType {
13 Insert,
15 Update,
17 Copy,
19 Move,
21 Delete,
23}
24
25impl EditType {
26 pub fn tag_name(&self) -> &'static str {
28 match self {
29 EditType::Insert => "insert",
30 EditType::Update => "update",
31 EditType::Copy => "copy",
32 EditType::Move => "move",
33 EditType::Delete => "delete",
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct EditEntry {
41 pub edit_type: EditType,
43 pub node: Option<NodeRef>,
45 pub is_left_tree: bool,
47}
48
49#[derive(Debug, Default)]
54pub struct EditLog {
55 edits: Vec<EditEntry>,
57 checkpoints: Vec<usize>,
59}
60
61impl EditLog {
62 pub fn new() -> Self {
64 EditLog {
65 edits: Vec::new(),
66 checkpoints: Vec::new(),
67 }
68 }
69
70 pub fn insert(&mut self, node: Option<NodeRef>) {
72 let is_left = node
73 .as_ref()
74 .map(crate::node::BranchNode::is_left_tree)
75 .unwrap_or(true);
76
77 self.edits.push(EditEntry {
78 edit_type: EditType::Insert,
79 node,
80 is_left_tree: is_left,
81 });
82 }
83
84 pub fn update(&mut self, node: Option<NodeRef>) {
86 let is_left = node
87 .as_ref()
88 .map(crate::node::BranchNode::is_left_tree)
89 .unwrap_or(true);
90
91 self.edits.push(EditEntry {
92 edit_type: EditType::Update,
93 node,
94 is_left_tree: is_left,
95 });
96 }
97
98 pub fn copy(&mut self, node: Option<NodeRef>) {
100 let is_left = node
101 .as_ref()
102 .map(crate::node::BranchNode::is_left_tree)
103 .unwrap_or(true);
104
105 self.edits.push(EditEntry {
106 edit_type: EditType::Copy,
107 node,
108 is_left_tree: is_left,
109 });
110 }
111
112 pub fn r#move(&mut self, node: Option<NodeRef>) {
114 let is_left = node
115 .as_ref()
116 .map(crate::node::BranchNode::is_left_tree)
117 .unwrap_or(true);
118
119 self.edits.push(EditEntry {
120 edit_type: EditType::Move,
121 node,
122 is_left_tree: is_left,
123 });
124 }
125
126 pub fn delete(&mut self, node: Option<NodeRef>) {
128 self.edits.push(EditEntry {
130 edit_type: EditType::Delete,
131 node,
132 is_left_tree: true,
133 });
134 }
135
136 pub fn checkpoint(&mut self) {
138 self.checkpoints.push(self.edits.len());
139 }
140
141 pub fn rewind(&mut self) {
143 if let Some(pos) = self.checkpoints.pop() {
144 self.edits.truncate(pos);
145 }
146 }
147
148 pub fn commit(&mut self) {
150 self.checkpoints.pop();
151 }
152
153 pub fn edit_count(&self) -> usize {
155 self.edits.len()
156 }
157
158 pub fn edits(&self) -> &[EditEntry] {
160 &self.edits
161 }
162
163 pub fn count_by_type(&self, edit_type: EditType) -> usize {
165 self.edits
166 .iter()
167 .filter(|e| e.edit_type == edit_type)
168 .count()
169 }
170
171 pub fn write_xml<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
173 writeln!(writer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
174 writeln!(writer, "<edits>")?;
175
176 for entry in &self.edits {
177 self.write_entry(writer, entry)?;
178 }
179
180 writeln!(writer, "</edits>")?;
181 Ok(())
182 }
183
184 fn write_entry<W: Write>(&self, writer: &mut W, entry: &EditEntry) -> std::io::Result<()> {
186 let tag = entry.edit_type.tag_name();
187 let tree = if entry.is_left_tree {
188 "branch1"
189 } else {
190 "branch2"
191 };
192
193 write!(writer, " <{}", tag)?;
194
195 if let Some(node) = &entry.node {
196 let path = get_node_path(node);
197 write!(writer, " path=\"{}\"", escape_xml(&path))?;
198
199 if entry.edit_type != EditType::Insert && entry.edit_type != EditType::Delete {
201 if let Some(base) = crate::node::BranchNode::base_match(node) {
202 let base_path = get_node_path(&base);
203 write!(writer, " src=\"{}\"", escape_xml(&base_path))?;
204 }
205 }
206 }
207
208 write!(writer, " originTree=\"{}\"", tree)?;
209 writeln!(writer, " />")?;
210
211 Ok(())
212 }
213}
214
215fn escape_xml(s: &str) -> String {
217 s.replace('&', "&")
218 .replace('<', "<")
219 .replace('>', ">")
220 .replace('"', """)
221}
222
223fn get_node_path(node: &NodeRef) -> String {
225 use crate::node::XmlContent;
226
227 let mut parts = Vec::new();
228 let mut current = Some(node.clone());
229
230 while let Some(n) = current {
231 let borrowed = n.borrow();
232 let name = match borrowed.content() {
233 Some(XmlContent::Element(e)) => e.qname().to_string(),
234 Some(XmlContent::Text(_)) => "#text".to_string(),
235 Some(XmlContent::Comment(_)) => "#comment".to_string(),
236 Some(XmlContent::ProcessingInstruction(pi)) => format!("#pi-{}", pi.target()),
237 None => "#node".to_string(),
238 };
239
240 let pos = borrowed.child_pos();
241 if pos >= 0 {
242 parts.push(format!("{}[{}]", name, pos));
243 } else {
244 parts.push(name);
245 }
246
247 current = borrowed.parent().upgrade();
248 }
249
250 parts.reverse();
251 if parts.is_empty() {
252 "/".to_string()
253 } else {
254 format!("/{}", parts.join("/"))
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_edit_type_tag_names() {
264 assert_eq!(EditType::Insert.tag_name(), "insert");
265 assert_eq!(EditType::Update.tag_name(), "update");
266 assert_eq!(EditType::Copy.tag_name(), "copy");
267 assert_eq!(EditType::Move.tag_name(), "move");
268 assert_eq!(EditType::Delete.tag_name(), "delete");
269 }
270
271 #[test]
272 fn test_edit_log_empty() {
273 let log = EditLog::new();
274 assert_eq!(log.edit_count(), 0);
275 }
276
277 #[test]
278 fn test_edit_log_operations() {
279 let mut log = EditLog::new();
280
281 log.insert(None);
282 log.update(None);
283 log.copy(None);
284 log.r#move(None);
285 log.delete(None);
286
287 assert_eq!(log.edit_count(), 5);
288 assert_eq!(log.count_by_type(EditType::Insert), 1);
289 assert_eq!(log.count_by_type(EditType::Update), 1);
290 assert_eq!(log.count_by_type(EditType::Copy), 1);
291 assert_eq!(log.count_by_type(EditType::Move), 1);
292 assert_eq!(log.count_by_type(EditType::Delete), 1);
293 }
294
295 #[test]
296 fn test_edit_log_checkpoint_rewind() {
297 let mut log = EditLog::new();
298
299 log.insert(None);
300 log.insert(None);
301 assert_eq!(log.edit_count(), 2);
302
303 log.checkpoint();
304 log.insert(None);
305 log.insert(None);
306 assert_eq!(log.edit_count(), 4);
307
308 log.rewind();
309 assert_eq!(log.edit_count(), 2);
310 }
311
312 #[test]
313 fn test_edit_log_checkpoint_commit() {
314 let mut log = EditLog::new();
315
316 log.insert(None);
317 log.checkpoint();
318 log.insert(None);
319 log.commit();
320 log.insert(None);
321
322 assert_eq!(log.edit_count(), 3);
323 }
324
325 #[test]
326 fn test_nested_checkpoints() {
327 let mut log = EditLog::new();
328
329 log.insert(None); log.checkpoint();
331 log.insert(None); log.checkpoint();
333 log.insert(None); assert_eq!(log.edit_count(), 3);
335
336 log.rewind(); assert_eq!(log.edit_count(), 2);
338
339 log.rewind(); assert_eq!(log.edit_count(), 1);
341 }
342}