1use crate::node::{LAttributeValue, LNode};
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Default)]
6struct OldChildren(IndexMap<LNode, Vec<usize>>);
7
8impl LNode {
9 pub fn diff(&self, other: &LNode) -> Vec<Patch> {
10 let mut old_children = OldChildren::default();
11 self.add_old_children(vec![], &mut old_children);
12 self.diff_at(other, &[], &old_children)
13 }
14
15 fn to_replacement_node(&self, old_children: &OldChildren) -> ReplacementNode {
16 match old_children.0.get(self) {
17 Some(path) => ReplacementNode::Path(path.to_owned()),
20 None => match self {
24 LNode::Fragment(fragment) => ReplacementNode::Fragment(fragment.iter().map(|node| node.to_replacement_node(old_children)).collect()),
25 LNode::Element { name, attrs, children } => ReplacementNode::Element {
26 name: name.to_owned(),
27 attrs: attrs
28 .iter()
29 .filter_map(|(name, value)| match value {
30 LAttributeValue::Boolean => Some((name.to_owned(), name.to_owned())),
31 LAttributeValue::Static(value) => Some((name.to_owned(), value.to_owned())),
32 _ => None,
33 })
34 .collect(),
35 children: children.iter().map(|node| node.to_replacement_node(old_children)).collect(),
36 },
37 LNode::Text(_) | LNode::Component { .. } | LNode::DynChild(_) => ReplacementNode::Html(self.to_html()),
38 },
39 }
40 }
41
42 fn add_old_children(&self, path: Vec<usize>, positions: &mut OldChildren) {
43 match self {
44 LNode::Fragment(frag) => {
45 for (idx, child) in frag.iter().enumerate() {
46 let mut new_path = path.clone();
47 new_path.push(idx);
48 child.add_old_children(new_path, positions);
49 }
50 }
51 LNode::Element { children, .. } => {
52 for (idx, child) in children.iter().enumerate() {
53 let mut new_path = path.clone();
54 new_path.push(idx);
55 child.add_old_children(new_path, positions);
56 }
57 }
58 LNode::DynChild(_) => {
60 positions.0.insert(self.clone(), path);
61 }
62 LNode::Component { children, .. } => {
63 positions.0.insert(self.clone(), path.clone());
64
65 for (idx, child) in children.iter().enumerate() {
66 let mut new_path = path.clone();
67 new_path.push(idx);
68 child.add_old_children(new_path, positions);
69 }
70 }
71 LNode::Text(_) => {}
73 }
74 }
75
76 fn diff_at(&self, other: &LNode, path: &[usize], orig_children: &OldChildren) -> Vec<Patch> {
77 if std::mem::discriminant(self) != std::mem::discriminant(other) {
78 return vec![Patch {
79 path: path.to_owned(),
80 action: PatchAction::ReplaceWith(other.to_replacement_node(orig_children)),
81 }];
82 }
83 match (self, other) {
84 (LNode::Fragment(old), LNode::Fragment(new)) => LNode::diff_children(path, old, new, orig_children),
86 (LNode::Text(_), LNode::Text(new)) => vec![Patch {
88 path: path.to_owned(),
89 action: PatchAction::SetText(new.to_owned()),
90 }],
91 (
93 LNode::Element {
94 name: old_name,
95 attrs: old_attrs,
96 children: old_children,
97 },
98 LNode::Element {
99 name: new_name,
100 attrs: new_attrs,
101 children: new_children,
102 },
103 ) => {
104 let tag_patch = (old_name != new_name).then(|| Patch {
105 path: path.to_owned(),
106 action: PatchAction::ChangeTagName(new_name.to_owned()),
107 });
108
109 let attrs_patch = LNode::diff_attrs(path, old_attrs, new_attrs);
110
111 let children_patch = LNode::diff_children(path, old_children, new_children, orig_children);
112
113 attrs_patch
114 .into_iter()
115 .chain(tag_patch)
117 .chain(children_patch)
118 .collect()
119 }
120 (
122 LNode::Component {
123 name: old_name,
124 children: old_children,
125 ..
126 },
127 LNode::Component {
128 name: new_name,
129 children: new_children,
130 ..
131 },
132 ) if old_name == new_name => {
133 let mut path = path.to_vec();
134 path.push(0);
135 path.push(0);
136 LNode::diff_children(&path, old_children, new_children, orig_children)
137 }
138 _ => vec![],
139 }
140 }
141
142 fn diff_attrs<'a>(
143 path: &'a [usize],
144 old: &'a [(String, LAttributeValue)],
145 new: &'a [(String, LAttributeValue)],
146 ) -> impl Iterator<Item = Patch> + 'a {
147 let additions = new
148 .iter()
149 .filter_map(|(name, new_value)| {
150 let old_attr = old.iter().find(|(o_name, _)| o_name == name);
151 let replace = match old_attr {
152 None => true,
153 Some((_, old_value)) if old_value != new_value => true,
154 _ => false,
155 };
156 if replace {
157 match &new_value {
158 LAttributeValue::Boolean => Some((name.to_owned(), "".to_string())),
159 LAttributeValue::Static(s) => Some((name.to_owned(), s.to_owned())),
160 _ => None,
161 }
162 } else {
163 None
164 }
165 })
166 .map(|(name, value)| Patch {
167 path: path.to_owned(),
168 action: PatchAction::SetAttribute(name, value),
169 });
170
171 let removals = old.iter().filter_map(|(name, _)| {
172 if !new.iter().any(|(new_name, _)| new_name == name) {
173 Some(Patch {
174 path: path.to_owned(),
175 action: PatchAction::RemoveAttribute(name.to_owned()),
176 })
177 } else {
178 None
179 }
180 });
181
182 additions.chain(removals)
183 }
184
185 fn diff_children(path: &[usize], old: &[LNode], new: &[LNode], old_children: &OldChildren) -> Vec<Patch> {
186 if old.is_empty() && new.is_empty() {
187 vec![]
188 } else if old.is_empty() {
189 vec![Patch {
190 path: path.to_owned(),
191 action: PatchAction::AppendChildren(new.iter().map(LNode::to_html).map(ReplacementNode::Html).collect()),
192 }]
193 } else if new.is_empty() {
194 vec![Patch {
195 path: path.to_owned(),
196 action: PatchAction::ClearChildren,
197 }]
198 } else {
199 let mut a = 0;
200 let mut b = std::cmp::max(old.len(), new.len()) - 1; let mut patches = vec![];
202 while a < b {
204 let old = old.get(a);
205 let new = new.get(a);
206
207 match (old, new) {
208 (None, None) => {}
209 (None, Some(new)) => patches.push(Patch {
210 path: path.to_owned(),
211 action: PatchAction::InsertChild {
212 before: a,
213 child: new.to_replacement_node(old_children),
214 },
215 }),
216 (Some(_), None) => patches.push(Patch {
217 path: path.to_owned(),
218 action: PatchAction::RemoveChild { at: a },
219 }),
220 (Some(old), Some(new)) => {
221 if old != new {
222 break;
223 }
224 }
225 }
226
227 a += 1;
228 }
229
230 while b >= a {
232 let old = old.get(b);
233 let new = new.get(b);
234
235 match (old, new) {
236 (None, None) => {}
237 (None, Some(new)) => patches.push(Patch {
238 path: path.to_owned(),
239 action: PatchAction::InsertChildAfter {
240 after: b - 1,
241 child: new.to_replacement_node(old_children),
242 },
243 }),
244 (Some(_), None) => patches.push(Patch {
245 path: path.to_owned(),
246 action: PatchAction::RemoveChild { at: b },
247 }),
248 (Some(old), Some(new)) => {
249 if old != new {
250 break;
251 }
252 }
253 }
254
255 if b == 0 {
256 break;
257 } else {
258 b -= 1;
259 }
260 }
261
262 if b >= a {
264 let old_slice_end = if b >= old.len() { old.len() - 1 } else { b };
265 let new_slice_end = if b >= new.len() { new.len() - 1 } else { b };
266 let old = &old[a..=old_slice_end];
267 let new = &new[a..=new_slice_end];
268
269 for (new_idx, new_node) in new.iter().enumerate() {
270 match old.get(new_idx) {
271 Some(old_node) => {
272 let mut new_path = path.to_vec();
273 new_path.push(new_idx + a);
274 let diffs = old_node.diff_at(new_node, &new_path, old_children);
275 patches.extend(&mut diffs.into_iter());
276 }
277 None => patches.push(Patch {
278 path: path.to_owned(),
279 action: PatchAction::InsertChild {
280 before: new_idx,
281 child: new_node.to_replacement_node(old_children),
282 },
283 }),
284 }
285 }
286 }
287
288 patches
289 }
290 }
291}
292
293#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
294pub struct Patches(pub Vec<(String, Vec<Patch>)>);
295
296#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
297pub struct Patch {
298 path: Vec<usize>,
299 action: PatchAction,
300}
301
302#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
303pub enum PatchAction {
304 ReplaceWith(ReplacementNode),
305 ChangeTagName(String),
306 RemoveAttribute(String),
307 SetAttribute(String, String),
308 SetText(String),
309 ClearChildren,
310 AppendChildren(Vec<ReplacementNode>),
311 RemoveChild { at: usize },
312 InsertChild { before: usize, child: ReplacementNode },
313 InsertChildAfter { after: usize, child: ReplacementNode },
314}
315
316#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
317pub enum ReplacementNode {
318 Html(String),
319 Path(Vec<usize>),
320 Fragment(Vec<ReplacementNode>),
321 Element {
322 name: String,
323 attrs: Vec<(String, String)>,
324 children: Vec<ReplacementNode>,
325 },
326}
327
328#[cfg(test)]
329mod tests {
330 use crate::{
331 diff::{Patch, PatchAction, ReplacementNode},
332 node::LAttributeValue,
333 LNode,
334 };
335
336 #[test]
337 fn patches_text() {
338 let a = LNode::Text("foo".into());
339 let b = LNode::Text("bar".into());
340 let delta = a.diff(&b);
341 assert_eq!(
342 delta,
343 vec![Patch {
344 path: vec![],
345 action: PatchAction::SetText("bar".into())
346 }]
347 );
348 }
349
350 #[test]
351 fn patches_attrs() {
352 let a = LNode::Element {
353 name: "button".into(),
354 attrs: vec![
355 ("class".into(), LAttributeValue::Static("a".into())),
356 ("type".into(), LAttributeValue::Static("button".into())),
357 ],
358 children: vec![],
359 };
360 let b = LNode::Element {
361 name: "button".into(),
362 attrs: vec![
363 ("class".into(), LAttributeValue::Static("a b".into())),
364 ("id".into(), LAttributeValue::Static("button".into())),
365 ],
366 children: vec![],
367 };
368 let delta = a.diff(&b);
369 assert_eq!(
370 delta,
371 vec![
372 Patch {
373 path: vec![],
374 action: PatchAction::SetAttribute("class".into(), "a b".into())
375 },
376 Patch {
377 path: vec![],
378 action: PatchAction::SetAttribute("id".into(), "button".into())
379 },
380 Patch {
381 path: vec![],
382 action: PatchAction::RemoveAttribute("type".into())
383 },
384 ]
385 );
386 }
387
388 #[test]
389 fn patches_child_text() {
390 let a = LNode::Element {
391 name: "button".into(),
392 attrs: vec![],
393 children: vec![LNode::Text("foo".into()), LNode::Text("bar".into())],
394 };
395 let b = LNode::Element {
396 name: "button".into(),
397 attrs: vec![],
398 children: vec![LNode::Text("foo".into()), LNode::Text("baz".into())],
399 };
400 let delta = a.diff(&b);
401 assert_eq!(
402 delta,
403 vec![Patch {
404 path: vec![1],
405 action: PatchAction::SetText("baz".into())
406 },]
407 );
408 }
409
410 #[test]
411 fn inserts_child() {
412 let a = LNode::Element {
413 name: "div".into(),
414 attrs: vec![],
415 children: vec![LNode::Element {
416 name: "button".into(),
417 attrs: vec![],
418 children: vec![LNode::Text("bar".into())],
419 }],
420 };
421 let b = LNode::Element {
422 name: "div".into(),
423 attrs: vec![],
424 children: vec![
425 LNode::Element {
426 name: "button".into(),
427 attrs: vec![],
428 children: vec![LNode::Text("foo".into())],
429 },
430 LNode::Element {
431 name: "button".into(),
432 attrs: vec![],
433 children: vec![LNode::Text("bar".into())],
434 },
435 ],
436 };
437 let delta = a.diff(&b);
438 assert_eq!(
439 delta,
440 vec![
441 Patch {
442 path: vec![],
443 action: PatchAction::InsertChildAfter {
444 after: 0,
445 child: ReplacementNode::Element {
446 name: "button".into(),
447 attrs: vec![],
448 children: vec![ReplacementNode::Html("bar".into())]
449 }
450 }
451 },
452 Patch {
453 path: vec![0, 0],
454 action: PatchAction::SetText("foo".into())
455 }
456 ]
457 );
458 }
459
460 #[test]
461 fn removes_child() {
462 let a = LNode::Element {
463 name: "div".into(),
464 attrs: vec![],
465 children: vec![
466 LNode::Element {
467 name: "button".into(),
468 attrs: vec![],
469 children: vec![LNode::Text("foo".into())],
470 },
471 LNode::Element {
472 name: "button".into(),
473 attrs: vec![],
474 children: vec![LNode::Text("bar".into())],
475 },
476 ],
477 };
478 let b = LNode::Element {
479 name: "div".into(),
480 attrs: vec![],
481 children: vec![LNode::Element {
482 name: "button".into(),
483 attrs: vec![],
484 children: vec![LNode::Text("foo".into())],
485 }],
486 };
487 let delta = a.diff(&b);
488 assert_eq!(
489 delta,
490 vec![Patch {
491 path: vec![],
492 action: PatchAction::RemoveChild { at: 1 }
493 },]
494 );
495 }
496}