mt_dom/
diff.rs

1//! provides diffing algorithm which returns patches
2use crate::{
3    node::attribute::group_attributes_per_name, Attribute, Element, Node,
4    Patch, TreePath,
5};
6use alloc::vec;
7use alloc::vec::Vec;
8use core::fmt::Debug;
9use core::{cmp, mem};
10
11/// Return the patches needed for `old_node` to have the same DOM as `new_node`
12///
13/// # Agruments
14/// * old_node - the old virtual dom node
15/// * new_node - the new virtual dom node
16/// * key - the literal name of key attribute, ie: "key"
17///
18/// # Example
19/// ```rust
20/// use mt_dom::{diff::*, patch::*, *};
21///
22/// pub type MyNode =
23///    Node<&'static str, &'static str, &'static str, &'static str, &'static str>;
24///
25/// let old: MyNode = element(
26///     "main",
27///     vec![attr("class", "container")],
28///     vec![
29///         element("div", vec![attr("key", "1")], vec![]),
30///         element("div", vec![attr("key", "2")], vec![]),
31///     ],
32/// );
33///
34/// let new: MyNode = element(
35///     "main",
36///     vec![attr("class", "container")],
37///     vec![element("div", vec![attr("key", "2")], vec![])],
38/// );
39///
40/// let diff = diff_with_key(&old, &new, &"key");
41/// assert_eq!(
42///     diff,
43///     vec![Patch::remove_node(
44///         Some(&"div"),
45///         TreePath::new(vec![ 0]),
46///     )
47///     ]
48/// );
49/// ```
50pub fn diff_with_key<'a, Ns, Tag, Leaf, Att, Val>(
51    old_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
52    new_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
53    key: &Att,
54) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
55where
56    Ns: PartialEq + Clone + Debug,
57    Tag: PartialEq + Debug,
58    Leaf: PartialEq + Clone + Debug,
59    Att: PartialEq + Clone + Debug,
60    Val: PartialEq + Clone + Debug,
61{
62    diff_recursive(
63        old_node,
64        new_node,
65        &TreePath::root(),
66        key,
67        &|_old, _new| false,
68        &|_old, _new| false,
69    )
70}
71
72/// calculate the difference of 2 nodes
73/// if the skip function evaluates to true, then diffing of
74/// the node and all of it's descendant will be skipped entirely and then proceed to the next node.
75///
76/// The Skip fn is passed to check whether the diffing of the old and new element should be
77/// skipped, and assumed no changes. This is for optimization where the developer is sure that
78/// the dom tree hasn't change.
79///
80/// Rep fn stands for replace function which decides if the new element should
81/// just replace the old element without diffing
82///
83pub fn diff_with_functions<'a, Ns, Tag, Leaf, Att, Val, Skip, Rep>(
84    old_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
85    new_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
86    key: &Att,
87    skip: &Skip,
88    rep: &Rep,
89) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
90where
91    Ns: PartialEq + Clone + Debug,
92    Tag: PartialEq + Debug,
93    Leaf: PartialEq + Clone + Debug,
94    Att: PartialEq + Clone + Debug,
95    Val: PartialEq + Clone + Debug,
96
97    Skip: Fn(
98        &'a Node<Ns, Tag, Leaf, Att, Val>,
99        &'a Node<Ns, Tag, Leaf, Att, Val>,
100    ) -> bool,
101    Rep: Fn(
102        &'a Node<Ns, Tag, Leaf, Att, Val>,
103        &'a Node<Ns, Tag, Leaf, Att, Val>,
104    ) -> bool,
105{
106    diff_recursive(old_node, new_node, &TreePath::root(), key, skip, rep)
107}
108
109fn is_any_keyed<Ns, Tag, Leaf, Att, Val>(
110    nodes: &[Node<Ns, Tag, Leaf, Att, Val>],
111    key: &Att,
112) -> bool
113where
114    Ns: PartialEq + Clone + Debug,
115    Tag: PartialEq + Debug,
116    Leaf: PartialEq + Clone + Debug,
117    Att: PartialEq + Clone + Debug,
118    Val: PartialEq + Clone + Debug,
119{
120    nodes.iter().any(|child| is_keyed_node(child, key))
121}
122
123/// returns true any attributes of this node attribute has key in it
124fn is_keyed_node<Ns, Tag, Leaf, Att, Val>(
125    node: &Node<Ns, Tag, Leaf, Att, Val>,
126    key: &Att,
127) -> bool
128where
129    Ns: PartialEq + Clone + Debug,
130    Tag: PartialEq + Debug,
131    Leaf: PartialEq + Clone + Debug,
132    Att: PartialEq + Clone + Debug,
133    Val: PartialEq + Clone + Debug,
134{
135    if let Some(attributes) = node.attributes() {
136        attributes.iter().any(|att| att.name == *key)
137    } else {
138        false
139    }
140}
141
142fn should_replace<'a, 'b, Ns, Tag, Leaf, Att, Val, Rep>(
143    old_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
144    new_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
145    key: &Att,
146    rep: &Rep,
147) -> bool
148where
149    Ns: PartialEq + Clone + Debug,
150    Tag: PartialEq + Debug,
151    Leaf: PartialEq + Clone + Debug,
152    Att: PartialEq + Clone + Debug,
153    Val: PartialEq + Clone + Debug,
154    Rep: Fn(
155        &'a Node<Ns, Tag, Leaf, Att, Val>,
156        &'a Node<Ns, Tag, Leaf, Att, Val>,
157    ) -> bool,
158{
159    // replace if they have different enum variants
160    if mem::discriminant(old_node) != mem::discriminant(new_node) {
161        return true;
162    }
163
164    // handle explicit replace if the Rep fn evaluates to true
165    if rep(old_node, new_node) {
166        return true;
167    }
168
169    // replace if the old key does not match the new key
170    if let (Some(old_key), Some(new_key)) =
171        (old_node.attribute_value(key), new_node.attribute_value(key))
172    {
173        if old_key != new_key {
174            return true;
175        }
176    }
177    // replace if they have different element tag
178    if let (Node::Element(old_element), Node::Element(new_element)) =
179        (old_node, new_node)
180    {
181        // Replace if there are different element tags
182        if old_element.tag != new_element.tag {
183            return true;
184        }
185    }
186    false
187}
188
189/// diff the nodes recursively
190pub fn diff_recursive<'a, 'b, Ns, Tag, Leaf, Att, Val, Skip, Rep>(
191    old_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
192    new_node: &'a Node<Ns, Tag, Leaf, Att, Val>,
193    path: &TreePath,
194    key: &Att,
195    skip: &Skip,
196    rep: &Rep,
197) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
198where
199    Ns: PartialEq + Clone + Debug,
200    Leaf: PartialEq + Clone + Debug,
201    Tag: PartialEq + Debug,
202    Att: PartialEq + Clone + Debug,
203    Val: PartialEq + Clone + Debug,
204    Skip: Fn(
205        &'a Node<Ns, Tag, Leaf, Att, Val>,
206        &'a Node<Ns, Tag, Leaf, Att, Val>,
207    ) -> bool,
208    Rep: Fn(
209        &'a Node<Ns, Tag, Leaf, Att, Val>,
210        &'a Node<Ns, Tag, Leaf, Att, Val>,
211    ) -> bool,
212{
213    // skip diffing if the function evaluates to true
214    if skip(old_node, new_node) {
215        return vec![];
216    }
217
218    // replace node and return early
219    if should_replace(old_node, new_node, key, rep) {
220        return vec![Patch::replace_node(
221            old_node.tag(),
222            path.clone(),
223            vec![new_node],
224        )];
225    }
226
227    // skip diffing if they are essentially the same node
228    if old_node == new_node {
229        return vec![];
230    }
231
232    let mut patches = vec![];
233
234    // The following comparison can only contain identical variants, other
235    // cases have already been handled above by comparing variant
236    // discriminants.
237    match (old_node, new_node) {
238        (Node::Leaf(old_leaf), Node::Leaf(new_leaf)) => {
239            if old_leaf != new_leaf {
240                let ct = Patch::replace_node(
241                    old_node.tag(),
242                    path.clone(),
243                    vec![new_node],
244                );
245                patches.push(ct);
246            }
247        }
248        // We're comparing two element nodes
249        (Node::Element(old_element), Node::Element(new_element)) => {
250            let patch =
251                diff_element(old_element, new_element, key, path, skip, rep);
252            patches.extend(patch);
253        }
254        (Node::Fragment(old_nodes), Node::Fragment(new_nodes)) => {
255            // we back track since Fragment is not a real node, but it would still
256            // be traversed from the prior call
257            let patch = diff_nodes(
258                None,
259                old_nodes,
260                new_nodes,
261                key,
262                &path.backtrack(),
263                skip,
264                rep,
265            );
266            patches.extend(patch);
267        }
268        (Node::NodeList(_old_elements), Node::NodeList(_new_elements)) => {
269            panic!(
270                "Node list must have already unrolled when creating an element"
271            );
272        }
273        _ => {
274            unreachable!("Unequal variant discriminants should already have been handled");
275        }
276    };
277
278    patches
279}
280
281fn diff_element<'a, Ns, Tag, Leaf, Att, Val, Skip, Rep>(
282    old_element: &'a Element<Ns, Tag, Leaf, Att, Val>,
283    new_element: &'a Element<Ns, Tag, Leaf, Att, Val>,
284    key: &Att,
285    path: &TreePath,
286    skip: &Skip,
287    rep: &Rep,
288) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
289where
290    Ns: PartialEq + Clone + Debug,
291    Tag: PartialEq + Debug,
292    Leaf: PartialEq + Clone + Debug,
293    Att: PartialEq + Clone + Debug,
294    Val: PartialEq + Clone + Debug,
295    Skip: Fn(
296        &'a Node<Ns, Tag, Leaf, Att, Val>,
297        &'a Node<Ns, Tag, Leaf, Att, Val>,
298    ) -> bool,
299    Rep: Fn(
300        &'a Node<Ns, Tag, Leaf, Att, Val>,
301        &'a Node<Ns, Tag, Leaf, Att, Val>,
302    ) -> bool,
303{
304    let mut patches = create_attribute_patches(old_element, new_element, path);
305
306    let more_patches = diff_nodes(
307        Some(old_element.tag()),
308        &old_element.children,
309        &new_element.children,
310        key,
311        path,
312        skip,
313        rep,
314    );
315
316    patches.extend(more_patches);
317    patches
318}
319
320fn diff_nodes<'a, Ns, Tag, Leaf, Att, Val, Skip, Rep>(
321    old_tag: Option<&'a Tag>,
322    old_children: &'a [Node<Ns, Tag, Leaf, Att, Val>],
323    new_children: &'a [Node<Ns, Tag, Leaf, Att, Val>],
324    key: &Att,
325    path: &TreePath,
326    skip: &Skip,
327    rep: &Rep,
328) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
329where
330    Ns: PartialEq + Clone + Debug,
331    Tag: PartialEq + Debug,
332    Leaf: PartialEq + Clone + Debug,
333    Att: PartialEq + Clone + Debug,
334    Val: PartialEq + Clone + Debug,
335    Skip: Fn(
336        &'a Node<Ns, Tag, Leaf, Att, Val>,
337        &'a Node<Ns, Tag, Leaf, Att, Val>,
338    ) -> bool,
339    Rep: Fn(
340        &'a Node<Ns, Tag, Leaf, Att, Val>,
341        &'a Node<Ns, Tag, Leaf, Att, Val>,
342    ) -> bool,
343{
344    let diff_as_keyed =
345        is_any_keyed(old_children, key) || is_any_keyed(new_children, key);
346
347    if diff_as_keyed {
348        let keyed_patches = crate::diff_lis::diff_keyed_nodes(
349            old_tag,
350            old_children,
351            new_children,
352            key,
353            path,
354            skip,
355            rep,
356        );
357        keyed_patches
358    } else {
359        let non_keyed_patches = diff_non_keyed_nodes(
360            old_tag,
361            old_children,
362            new_children,
363            key,
364            path,
365            skip,
366            rep,
367        );
368        non_keyed_patches
369    }
370}
371
372/// In diffing non_keyed nodes,
373///  we reuse existing DOM elements as much as possible
374///
375///  The algorithm used here is very simple.
376///
377///  If there are more children in the old_element than the new_element
378///  the excess children is all removed.
379///
380///  If there are more children in the new_element than the old_element
381///  it will be all appended in the old_element.
382fn diff_non_keyed_nodes<'a, Ns, Tag, Leaf, Att, Val, Skip, Rep>(
383    old_element_tag: Option<&'a Tag>,
384    old_children: &'a [Node<Ns, Tag, Leaf, Att, Val>],
385    new_children: &'a [Node<Ns, Tag, Leaf, Att, Val>],
386    key: &Att,
387    path: &TreePath,
388    skip: &Skip,
389    rep: &Rep,
390) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
391where
392    Ns: PartialEq + Clone + Debug,
393    Tag: PartialEq + Debug,
394    Leaf: PartialEq + Clone + Debug,
395    Att: PartialEq + Clone + Debug,
396    Val: PartialEq + Clone + Debug,
397    Skip: Fn(
398        &'a Node<Ns, Tag, Leaf, Att, Val>,
399        &'a Node<Ns, Tag, Leaf, Att, Val>,
400    ) -> bool,
401    Rep: Fn(
402        &'a Node<Ns, Tag, Leaf, Att, Val>,
403        &'a Node<Ns, Tag, Leaf, Att, Val>,
404    ) -> bool,
405{
406    let mut patches = vec![];
407    let old_child_count = old_children.len();
408    let new_child_count = new_children.len();
409
410    let min_count = cmp::min(old_child_count, new_child_count);
411    for index in 0..min_count {
412        // if we iterate trough the old elements, a new child_path is created for that iteration
413        let child_path = path.traverse(index);
414
415        let old_child =
416            &old_children.get(index).expect("No old_node child node");
417        let new_child = &new_children.get(index).expect("No new child node");
418
419        let more_patches =
420            diff_recursive(old_child, new_child, &child_path, key, skip, rep);
421        patches.extend(more_patches);
422    }
423
424    // If there are more new child than old_node child, we make a patch to append the excess element
425    // starting from old_child_count to the last item of the new_elements
426    if new_child_count > old_child_count {
427        patches.push(Patch::append_children(
428            old_element_tag,
429            path.clone(),
430            new_children.iter().skip(old_child_count).collect(),
431        ));
432    }
433
434    if new_child_count < old_child_count {
435        let remove_node_patches = old_children
436            .iter()
437            .skip(new_child_count)
438            .enumerate()
439            .map(|(i, old_child)| {
440                Patch::remove_node(
441                    old_child.tag(),
442                    path.traverse(new_child_count + i),
443                )
444            })
445            .collect::<Vec<_>>();
446
447        patches.extend(remove_node_patches);
448    }
449
450    patches
451}
452
453///
454/// Note: The performance bottlenecks
455///     - allocating new vec
456///     - merging attributes of the same name
457fn create_attribute_patches<'a, Ns, Tag, Leaf, Att, Val>(
458    old_element: &'a Element<Ns, Tag, Leaf, Att, Val>,
459    new_element: &'a Element<Ns, Tag, Leaf, Att, Val>,
460    path: &TreePath,
461) -> Vec<Patch<'a, Ns, Tag, Leaf, Att, Val>>
462where
463    Ns: PartialEq + Clone + Debug,
464    Leaf: PartialEq + Clone + Debug,
465    Tag: PartialEq + Debug,
466    Att: PartialEq + Clone + Debug,
467    Val: PartialEq + Clone + Debug,
468{
469    let new_attributes = new_element.attributes();
470    let old_attributes = old_element.attributes();
471
472    // skip diffing if they the same attributes
473    if old_attributes == new_attributes {
474        return vec![];
475    }
476    let mut patches = vec![];
477
478    let mut add_attributes: Vec<&Attribute<Ns, Att, Val>> = vec![];
479    let mut remove_attributes: Vec<&Attribute<Ns, Att, Val>> = vec![];
480
481    let new_attributes_grouped = group_attributes_per_name(new_attributes);
482    let old_attributes_grouped = group_attributes_per_name(old_attributes);
483
484    // for all new elements that doesn't exist in the old elements
485    // or the values differ
486    // add it to the AddAttribute patches
487    for (new_attr_name, new_attrs) in new_attributes_grouped.iter() {
488        let old_attr_values = old_attributes_grouped
489            .iter()
490            .find(|(att_name, _)| att_name == new_attr_name)
491            .map(|(_, attrs)| {
492                attrs.iter().map(|attr| &attr.value).collect::<Vec<_>>()
493            });
494
495        let new_attr_values = new_attributes_grouped
496            .iter()
497            .find(|(att_name, _)| att_name == new_attr_name)
498            .map(|(_, attrs)| {
499                attrs.iter().map(|attr| &attr.value).collect::<Vec<_>>()
500            });
501
502        if let Some(old_attr_values) = old_attr_values {
503            let new_attr_values =
504                new_attr_values.expect("must have new attr values");
505            if old_attr_values != new_attr_values {
506                add_attributes.extend(new_attrs);
507            }
508        } else {
509            add_attributes.extend(new_attrs);
510        }
511    }
512
513    // if this attribute name does not exist anymore
514    // to the new element, remove it
515    for (old_attr_name, old_attrs) in old_attributes_grouped.iter() {
516        if let Some(_pre_attr) = new_attributes_grouped
517            .iter()
518            .find(|(new_attr_name, _)| new_attr_name == old_attr_name)
519        {
520            //
521        } else {
522            remove_attributes.extend(old_attrs);
523        }
524    }
525
526    if !add_attributes.is_empty() {
527        patches.push(Patch::add_attributes(
528            &old_element.tag,
529            path.clone(),
530            add_attributes,
531        ));
532    }
533    if !remove_attributes.is_empty() {
534        patches.push(Patch::remove_attributes(
535            &old_element.tag,
536            path.clone(),
537            remove_attributes,
538        ));
539    }
540    patches
541}