use crate::Attribute;
use crate::Element;
use crate::Node;
use std::cmp;
use std::fmt::Debug;
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, 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 + Debug,
ATT: PartialEq + Debug,
NS: PartialEq + Debug,
VAL: PartialEq + Debug,
{
diff_recursive(old, new, &mut 0, key)
}
fn increment_node_idx_for_children<NS, TAG, ATT, VAL, EVENT, MSG>(
old: &Node<NS, TAG, 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, 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 + Debug,
TAG: PartialEq + Debug,
ATT: PartialEq + Debug,
VAL: PartialEq + 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;
}
let new_attributes = new_element.get_attributes();
let old_attributes = old_element.get_attributes();
let old_key_value = old_attributes.iter().find(|att| att.name == *key);
let new_key_value = new_attributes.iter().find(|att| att.name == *key);
match (old_key_value, new_key_value) {
(Some(old_kv), Some(new_kv)) => {
if old_kv != new_kv {
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<NS, TAG, 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 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 + Debug,
ATT: PartialEq + Debug,
VAL: PartialEq + 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 = new_element.get_attributes();
let old_attributes = old_element.get_attributes();
for new_attr in new_attributes.iter() {
let old_attr_value = old_attributes
.iter()
.find(|att| att.name == new_attr.name)
.map(|att| &att.value);
if let Some(old_attr_value) = old_attr_value {
if *old_attr_value != new_attr.value {
add_attributes.push(new_attr);
}
} else {
add_attributes.push(new_attr);
}
}
for old_attr in old_element.get_attributes().iter() {
if let Some(_pre_attr) = new_attributes.iter().find(|att| att.name == old_attr.name) {
} else {
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
}