use std::{
fmt,
collections::BTreeSet,
};
use crate::{
id_tree::{NodeId, NodeDataContainer},
dom::{CompactDom, NodeData},
};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DomRange {
pub start: NodeId,
pub end: NodeId,
}
impl DomRange {
pub fn new(start: NodeId, end: NodeId) -> Self {
Self { start, end }
}
pub fn single_node(node_id: NodeId) -> Self {
Self {
start: node_id,
end: node_id,
}
}
}
impl fmt::Debug for DomRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
impl fmt::Display for DomRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DomChange {
Added(DomRange),
Removed(DomRange),
}
impl fmt::Display for DomChange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::DomChange::*;
match self {
Added(c) => write!(f, "+ {}", c),
Removed(c) => write!(f, "- {}", c),
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DomDiff {
pub changed_nodes: Vec<DomChange>,
}
impl DomDiff {
pub fn new(old: &CompactDom, new: &CompactDom) -> Self {
let mut changes = BTreeSet::new();
let mut visited_nodes = NodeDataContainer::new(vec![false; new.len()]);
visited_nodes[NodeId::ZERO] = true;
let has_root_changed = node_has_changed(
&old.arena.node_data[NodeId::ZERO],
&new.arena.node_data[NodeId::ZERO]
);
if has_root_changed == NODE_CHANGED_NOTHING {
diff_tree_inner(NodeId::ZERO, old, new, &mut changes, &mut visited_nodes);
add_visited_nodes(visited_nodes, &mut changes);
Self {
changed_nodes: optimize_changeset(changes)
}
} else {
changes.insert(DomChange::Removed(DomRange {
start: NodeId::ZERO,
end: NodeId::new(old.len() - 1)
}));
changes.insert(DomChange::Added(DomRange {
start: NodeId::ZERO,
end: NodeId::new(new.len() - 1)
}));
Self {
changed_nodes: optimize_changeset(changes)
}
}
}
pub fn format_nicely(&self, old: &CompactDom, new: &CompactDom) -> String {
use self::DomChange::*;
self.changed_nodes.iter().map(|change| {
match change {
Added(c) => format!("+\t{}", new.arena.node_data[c.start]),
Removed(c) => format!("-\t{}", old.arena.node_data[c.start]),
}
}).collect::<Vec<String>>().join("\r\n")
}
}
impl fmt::Display for DomDiff {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for c in self.changed_nodes.iter() {
write!(f, "{}", c)?;
}
Ok(())
}
}
impl DomRange {
pub fn contains(&self, other: &Self) -> bool {
other.start.index() >= self.start.index() &&
other.end.index() <= self.end.index()
}
pub fn equals_range(&self, other: &Self) -> bool {
other.start == self.start &&
other.end == self.end
}
}
const NODE_CHANGED_NOTHING: u8 = 0x01;
const NODE_CHANGED_TYPE: u8 = 0x02;
const NODE_CHANGED_CLASSES: u8 = 0x04;
const NODE_CHANGED_IDS: u8 = 0x08;
#[inline]
fn node_has_changed(old: &NodeData, new: &NodeData) -> u8 {
let mut result = NODE_CHANGED_NOTHING;
if old.get_node_type() != new.get_node_type() {
result &= NODE_CHANGED_TYPE;
}
if old.get_classes() != new.get_classes() {
result &= NODE_CHANGED_CLASSES;
}
if old.get_ids() != new.get_ids() {
result &= NODE_CHANGED_IDS;
}
result
}
fn diff_tree_inner(
old_root_id: NodeId,
old: &CompactDom,
new: &CompactDom,
changes: &mut BTreeSet<DomChange>,
visited_nodes: &mut NodeDataContainer<bool>,
) {
let mut node_shift = 0_isize;
for old_node_id in old_root_id.children(&old.arena.node_hierarchy) {
let new_node_id = NodeId::new((old_node_id.index() as isize + node_shift) as usize);
let old_node_last_child = NodeId::new(match old.arena.node_hierarchy[old_node_id].next_sibling {
Some(s) => s.index(),
None => old.arena.node_hierarchy.len() - 1,
});
match new.arena.node_data.get(new_node_id) {
None => {
changes.insert(DomChange::Removed(DomRange {
start: old_node_id,
end: old_node_last_child,
}));
},
Some(new_node_data) => {
visited_nodes[new_node_id] = true;
let old_node_data = &old.arena.node_data[old_node_id];
let compare_nodes = node_has_changed(old_node_data, new_node_data);
if compare_nodes == NODE_CHANGED_NOTHING {
diff_tree_inner(old_node_id, old, new, changes, visited_nodes);
} else {
let new_node_subtree_len = new.arena.node_hierarchy.subtree_len(new_node_id);
let next_node_id = match new.arena.node_hierarchy[new_node_id].next_sibling {
Some(s) => s.index(),
None => new.arena.node_hierarchy.len() - 1,
};
changes.insert(DomChange::Removed(DomRange {
start: old_node_id,
end: old_node_last_child,
}));
changes.insert(DomChange::Added(DomRange {
start: new_node_id,
end: NodeId::new(next_node_id),
}));
node_shift += new_node_subtree_len as isize;
for n in new_node_id.index()..next_node_id {
visited_nodes[NodeId::new(n)] = true;
}
}
}
}
}
}
fn add_visited_nodes(
visited_nodes: NodeDataContainer<bool>,
changes: &mut BTreeSet<DomChange>,
) {
changes.extend(
visited_nodes
.linear_iter()
.filter_map(|node_id| if visited_nodes[node_id] { None } else { Some(node_id) })
.map(|node_id| DomChange::Added(DomRange::single_node(node_id)))
);
}
fn optimize_changeset(changes: BTreeSet<DomChange>) -> Vec<DomChange> {
changes.into_iter().collect()
}