use crate::Attribute;
use crate::Element;
use crate::Node;
use std::cmp;
use std::mem;
#[derive(Debug, PartialEq)]
pub enum Patch<'a, NS, TAG, ATT, VAL, EVENT, MSG> {
AppendChildren(
&'a TAG,
NodeIdx,
Vec<&'a Node<NS, TAG, ATT, VAL, EVENT, MSG>>,
),
TruncateChildren(&'a TAG, NodeIdx, usize),
Replace(&'a TAG, NodeIdx, &'a Node<NS, TAG, ATT, VAL, EVENT, MSG>),
AddAttributes(
&'a TAG,
NodeIdx,
Vec<&'a Attribute<NS, ATT, VAL, EVENT, MSG>>,
),
RemoveAttributes(
&'a TAG,
NodeIdx,
Vec<&'a Attribute<NS, ATT, VAL, EVENT, MSG>>,
),
ChangeText(NodeIdx, &'a str),
}
type NodeIdx = usize;
impl<'a, NS, TAG, ATT, VAL, EVENT, MSG> Patch<'a, NS, TAG, ATT, VAL, EVENT, MSG> {
pub fn node_idx(&self) -> NodeIdx {
match self {
Patch::AppendChildren(_tag, node_idx, _) => *node_idx,
Patch::TruncateChildren(_tag, node_idx, _) => *node_idx,
Patch::Replace(_tag, node_idx, _) => *node_idx,
Patch::AddAttributes(_tag, node_idx, _) => *node_idx,
Patch::RemoveAttributes(_tag, node_idx, _) => *node_idx,
Patch::ChangeText(node_idx, _) => *node_idx,
}
}
pub fn tag(&self) -> Option<&TAG> {
match self {
Patch::AppendChildren(tag, _node_idx, _) => Some(tag),
Patch::TruncateChildren(tag, _node_idx, _) => Some(tag),
Patch::Replace(tag, _node_idx, _) => Some(tag),
Patch::AddAttributes(tag, _node_idx, _) => Some(tag),
Patch::RemoveAttributes(tag, _node_idx, _) => Some(tag),
Patch::ChangeText(_node_idx, _) => None,
}
}
}
pub fn diff_with_key<'a, 'b, TAG, NS, ATT, VAL, EVENT, MSG>(
old: &'a Node<TAG, NS, ATT, VAL, EVENT, MSG>,
new: &'a Node<TAG, NS, ATT, VAL, EVENT, MSG>,
key: &ATT,
) -> Vec<Patch<'a, TAG, NS, ATT, VAL, EVENT, MSG>>
where
TAG: PartialEq,
ATT: PartialEq,
NS: PartialEq,
VAL: PartialEq,
{
diff_recursive(old, new, &mut 0, key)
}
fn increment_node_idx_for_children<TAG, NS, ATT, VAL, EVENT, MSG>(
old: &Node<TAG, NS, ATT, VAL, EVENT, MSG>,
cur_node_idx: &mut usize,
) {
*cur_node_idx += 1;
if let Node::Element(element_node) = old {
for child in element_node.children.iter() {
increment_node_idx_for_children(&child, cur_node_idx);
}
}
}
fn diff_recursive<'a, 'b, TAG, NS, ATT, VAL, EVENT, MSG>(
old: &'a Node<TAG, NS, ATT, VAL, EVENT, MSG>,
new: &'a Node<TAG, NS, ATT, VAL, EVENT, MSG>,
cur_node_idx: &'b mut usize,
key: &ATT,
) -> Vec<Patch<'a, TAG, NS, ATT, VAL, EVENT, MSG>>
where
TAG: PartialEq,
ATT: PartialEq,
NS: PartialEq,
VAL: PartialEq,
{
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;
}
let old_key_value = old_element.get_attribute_values(key);
let new_key_value = new_element.get_attribute_values(key);
if !is_same_set(&old_key_value, &new_key_value) {
replace = true;
}
}
if replace {
patches.push(Patch::Replace(
old.tag().expect("must have a tag"),
*cur_node_idx,
&new,
));
if let Node::Element(old_element_node) = old {
for child in old_element_node.children.iter() {
increment_node_idx_for_children(child, cur_node_idx);
}
}
return patches;
}
match (old, new) {
(Node::Text(old_text), Node::Text(new_text)) => {
if old_text != new_text {
patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
}
}
(Node::Element(old_element), Node::Element(new_element)) => {
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<TAG, NS, ATT, VAL, EVENT, MSG>> =
new_element.children[old_child_count..].iter().collect();
patches.push(Patch::AppendChildren(
&old_element.tag,
*cur_node_idx,
append_patch,
))
}
if new_child_count < old_child_count {
patches.push(Patch::TruncateChildren(
&old_element.tag,
*cur_node_idx,
new_child_count,
))
}
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");
patches.append(&mut diff_recursive(
&old_child,
&new_child,
cur_node_idx,
key,
))
}
if new_child_count < old_child_count {
for child in old_element.children[min_count..].iter() {
increment_node_idx_for_children(child, cur_node_idx);
}
}
}
(Node::Text(_), Node::Element(_)) | (Node::Element(_), Node::Text(_)) => {
unreachable!("Unequal variant discriminants should already have been handled");
}
};
patches
}
fn is_same_set<T: PartialEq>(set1: &[&T], set2: &[&T]) -> bool {
set1.iter().all(|item1| set2.contains(item1)) && set2.iter().all(|item2| set1.contains(item2))
}
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
ATT: PartialEq,
VAL: PartialEq,
{
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![];
for new_attr in new_element.get_attributes().iter() {
let old_attr_value = old_element.get_attribute_values(&new_attr.name);
let new_attr_value = new_element.get_attribute_values(&new_attr.name);
if old_attr_value.is_empty() || !is_same_set(&old_attr_value, &new_attr_value) {
add_attributes.push(new_attr);
}
}
for old_attr in old_element.get_attributes().iter() {
if new_element.get_attribute_values(&old_attr.name).is_empty() {
remove_attributes.push(&old_attr);
}
}
if !add_attributes.is_empty() {
patches.push(Patch::AddAttributes(
&old_element.tag,
*cur_node_idx,
add_attributes,
));
}
if !remove_attributes.is_empty() {
patches.push(Patch::RemoveAttributes(
&old_element.tag,
*cur_node_idx,
remove_attributes,
));
}
patches
}