use crate::{
node::attribute::group_attributes_per_name,
patch::{
AddAttributes,
AppendChildren,
ChangeText,
RemoveAttributes,
RemoveNode,
ReplaceNode,
},
Attribute,
Element,
Node,
Patch,
};
use keyed_elements::diff_keyed_elements;
use std::{
cmp,
fmt,
mem,
};
mod keyed_elements;
pub fn diff_with_key<'a, NS, TAG, ATT, VAL, EVENT, MSG>(
old: &'a Node<NS, TAG, ATT, VAL, EVENT, MSG>,
new: &'a Node<NS, TAG, ATT, VAL, EVENT, MSG>,
key: &ATT,
) -> Vec<Patch<'a, NS, TAG, ATT, VAL, EVENT, MSG>>
where
TAG: PartialEq + fmt::Debug,
ATT: PartialEq + fmt::Debug,
NS: PartialEq + fmt::Debug,
VAL: PartialEq + fmt::Debug,
{
diff_recursive(old, new, &mut 0, key)
}
pub(crate) fn increment_node_idx_to_descendant_count<
NS,
TAG,
ATT,
VAL,
EVENT,
MSG,
>(
node: &Node<NS, TAG, ATT, VAL, EVENT, MSG>,
cur_node_idx: &mut usize,
) {
match node {
Node::Element(element_node) => {
for child in element_node.get_children().iter() {
*cur_node_idx += 1;
increment_node_idx_to_descendant_count(&child, cur_node_idx);
}
}
Node::Text(_txt) => {
}
}
}
fn is_any_children_keyed<'a, NS, TAG, ATT, VAL, EVENT, MSG>(
element: &'a Element<NS, TAG, ATT, VAL, EVENT, MSG>,
key: &ATT,
) -> bool
where
ATT: PartialEq,
{
element
.get_children()
.iter()
.any(|child| is_keyed_node(child, key))
}
fn is_keyed_node<'a, NS, TAG, ATT, VAL, EVENT, MSG>(
node: &'a Node<NS, TAG, ATT, VAL, EVENT, MSG>,
key: &ATT,
) -> bool
where
ATT: PartialEq,
{
if let Some(attributes) = node.get_attributes() {
attributes.iter().any(|att| att.name == *key)
} else {
false
}
}
fn diff_recursive<'a, 'b, NS, TAG, ATT, VAL, EVENT, MSG>(
old: &'a Node<NS, TAG, ATT, VAL, EVENT, MSG>,
new: &'a Node<NS, TAG, ATT, VAL, EVENT, MSG>,
cur_node_idx: &'b mut usize,
key: &ATT,
) -> Vec<Patch<'a, NS, TAG, ATT, VAL, EVENT, MSG>>
where
NS: PartialEq + fmt::Debug,
TAG: PartialEq + fmt::Debug,
ATT: PartialEq + fmt::Debug,
VAL: PartialEq + fmt::Debug,
{
let mut patches = vec![];
let mut replace = mem::discriminant(old) != mem::discriminant(new);
if let (Node::Element(old_element), Node::Element(new_element)) = (old, new)
{
if old_element.tag != new_element.tag {
replace = true;
}
}
if replace {
patches.push(
ReplaceNode::new(
old.tag().expect("must have a tag"),
*cur_node_idx,
&new,
)
.into(),
);
increment_node_idx_to_descendant_count(old, cur_node_idx);
return patches;
}
match (old, new) {
(Node::Text(old_text), Node::Text(new_text)) => {
if old_text != new_text {
let ct = ChangeText::new(*cur_node_idx, old_text, new_text);
patches.push(Patch::ChangeText(ct));
}
}
(Node::Element(old_element), Node::Element(new_element)) => {
if is_any_children_keyed(old_element, key)
|| is_any_children_keyed(new_element, key)
{
let keyed_patches = diff_keyed_elements(
old_element,
new_element,
key,
cur_node_idx,
);
patches.extend(keyed_patches);
} else {
let non_keyed_patches = diff_non_keyed_elements(
old_element,
new_element,
key,
cur_node_idx,
);
patches.extend(non_keyed_patches);
}
}
(Node::Text(_), Node::Element(_))
| (Node::Element(_), Node::Text(_)) => {
unreachable!("Unequal variant discriminants should already have been handled");
}
};
patches
}
fn diff_non_keyed_elements<'a, 'b, NS, TAG, ATT, VAL, EVENT, MSG>(
old_element: &'a Element<NS, TAG, ATT, VAL, EVENT, MSG>,
new_element: &'a Element<NS, TAG, ATT, VAL, EVENT, MSG>,
key: &ATT,
cur_node_idx: &'b mut usize,
) -> Vec<Patch<'a, NS, TAG, ATT, VAL, EVENT, MSG>>
where
NS: PartialEq + fmt::Debug,
TAG: PartialEq + fmt::Debug,
ATT: PartialEq + fmt::Debug,
VAL: PartialEq + fmt::Debug,
{
let mut patches = vec![];
let attributes_patches =
diff_attributes(old_element, new_element, cur_node_idx);
patches.extend(attributes_patches);
let old_child_count = old_element.children.len();
let new_child_count = new_element.children.len();
if new_child_count > old_child_count {
let append_patch: Vec<&'a Node<NS, TAG, ATT, VAL, EVENT, MSG>> =
new_element.children[old_child_count..].iter().collect();
patches.push(
AppendChildren::new(&old_element.tag, *cur_node_idx, append_patch)
.into(),
)
}
let min_count = cmp::min(old_child_count, new_child_count);
for index in 0..min_count {
*cur_node_idx += 1;
let old_child =
&old_element.children.get(index).expect("No old child node");
let new_child =
&new_element.children.get(index).expect("No new chold node");
let more_patches =
diff_recursive(old_child, new_child, cur_node_idx, key);
patches.extend(more_patches);
}
if new_child_count < old_child_count {
for old_child in old_element.get_children().iter().skip(new_child_count)
{
*cur_node_idx += 1;
patches
.push(RemoveNode::new(old_child.tag(), *cur_node_idx).into());
increment_node_idx_to_descendant_count(old_child, cur_node_idx);
}
}
patches
}
fn diff_attributes<'a, 'b, NS, TAG, ATT, VAL, EVENT, MSG>(
old_element: &'a Element<NS, TAG, ATT, VAL, EVENT, MSG>,
new_element: &'a Element<NS, TAG, ATT, VAL, EVENT, MSG>,
cur_node_idx: &'b mut usize,
) -> Vec<Patch<'a, NS, TAG, ATT, VAL, EVENT, MSG>>
where
NS: PartialEq + fmt::Debug,
ATT: PartialEq + fmt::Debug,
VAL: PartialEq + fmt::Debug,
{
let mut patches = vec![];
let mut add_attributes: Vec<&Attribute<NS, ATT, VAL, EVENT, MSG>> = vec![];
let mut remove_attributes: Vec<&Attribute<NS, ATT, VAL, EVENT, MSG>> =
vec![];
let new_attributes_grouped =
group_attributes_per_name(new_element.get_attributes());
let old_attributes_grouped =
group_attributes_per_name(old_element.get_attributes());
for (new_attr_name, new_attrs) in new_attributes_grouped.iter() {
let old_attr_values = old_attributes_grouped
.iter()
.find(|(att_name, _)| att_name == new_attr_name)
.map(|(_, attrs)| {
attrs.iter().map(|attr| &attr.value).collect::<Vec<_>>()
});
let new_attr_values = new_attributes_grouped
.iter()
.find(|(att_name, _)| att_name == new_attr_name)
.map(|(_, attrs)| {
attrs.iter().map(|attr| &attr.value).collect::<Vec<_>>()
});
if let Some(old_attr_values) = old_attr_values {
let new_attr_values =
new_attr_values.expect("must have new attr values");
if old_attr_values != new_attr_values {
add_attributes.extend(new_attrs);
}
} else {
add_attributes.extend(new_attrs);
}
}
for (old_attr_name, old_attrs) in old_attributes_grouped.iter() {
if let Some(_pre_attr) = new_attributes_grouped
.iter()
.find(|(new_attr_name, _)| new_attr_name == old_attr_name)
{
} else {
remove_attributes.extend(old_attrs);
}
}
if !add_attributes.is_empty() {
patches.push(
AddAttributes::new(&old_element.tag, *cur_node_idx, add_attributes)
.into(),
);
}
if !remove_attributes.is_empty() {
patches.push(
RemoveAttributes::new(
&old_element.tag,
*cur_node_idx,
remove_attributes,
)
.into(),
);
}
patches
}