use crate::node::{Mark, Node};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::{HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "camelCase")]
pub enum Change {
SetAttr {
path: Vec<usize>,
key: String,
value: Value,
},
RemoveAttr {
path: Vec<usize>,
key: String,
},
SetText {
path: Vec<usize>,
text: Option<String>,
},
SetMarks {
path: Vec<usize>,
marks: Option<Vec<Mark>>,
},
SetExtra {
path: Vec<usize>,
key: String,
value: Value,
},
RemoveExtra {
path: Vec<usize>,
key: String,
},
Insert {
path: Vec<usize>,
index: usize,
node: Node,
},
Remove {
path: Vec<usize>,
index: usize,
},
Replace {
path: Vec<usize>,
node: Node,
},
Move {
path: Vec<usize>,
from: usize,
to: usize,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApplyError {
pub path: Vec<usize>,
}
impl fmt::Display for ApplyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "apply: no node at path {:?}", self.path)
}
}
impl std::error::Error for ApplyError {}
impl Node {
pub fn diff(&self, other: &Node) -> Vec<Change> {
let mut out = Vec::new();
let mut path = Vec::new();
diff_node(self, other, &mut path, &mut out);
out
}
pub fn apply(&mut self, changes: &[Change]) -> std::result::Result<(), ApplyError> {
apply(self, changes)
}
pub fn invert(&self, changes: &[Change]) -> std::result::Result<Vec<Change>, ApplyError> {
invert(self, changes)
}
}
pub fn diff(a: &Node, b: &Node) -> Vec<Change> {
a.diff(b)
}
pub fn apply(root: &mut Node, changes: &[Change]) -> std::result::Result<(), ApplyError> {
for change in changes {
apply_one(root, change)?;
}
Ok(())
}
pub fn invert(base: &Node, changes: &[Change]) -> std::result::Result<Vec<Change>, ApplyError> {
let mut result = base.clone();
apply(&mut result, changes)?;
Ok(result.diff(base))
}
fn diff_node(a: &Node, b: &Node, path: &mut Vec<usize>, out: &mut Vec<Change>) {
if a == b {
return; }
if a.node_type != b.node_type
|| empty_shape_mismatch(
a.attrs.as_ref().map(Map::is_empty),
b.attrs.as_ref().map(Map::is_empty),
)
|| empty_shape_mismatch(
a.content.as_ref().map(Vec::is_empty),
b.content.as_ref().map(Vec::is_empty),
)
{
out.push(Change::Replace {
path: path.clone(),
node: b.clone(),
});
return;
}
diff_attrs(a.attrs.as_ref(), b.attrs.as_ref(), path, out);
if a.text != b.text {
out.push(Change::SetText {
path: path.clone(),
text: b.text.clone(),
});
}
if a.marks != b.marks {
out.push(Change::SetMarks {
path: path.clone(),
marks: b.marks.clone(),
});
}
diff_extra(&a.extra, &b.extra, path, out);
diff_children(a.children(), b.children(), path, out);
}
fn empty_shape_mismatch(a_is_empty: Option<bool>, b_is_empty: Option<bool>) -> bool {
match b_is_empty {
Some(true) => a_is_empty != Some(true),
None => a_is_empty == Some(true),
Some(false) => false,
}
}
fn diff_attrs(
a: Option<&Map<String, Value>>,
b: Option<&Map<String, Value>>,
path: &mut [usize],
out: &mut Vec<Change>,
) {
let empty = Map::new();
let am = a.unwrap_or(&empty);
let bm = b.unwrap_or(&empty);
for (k, v) in bm {
if am.get(k) != Some(v) {
out.push(Change::SetAttr {
path: path.to_vec(),
key: k.clone(),
value: v.clone(),
});
}
}
for k in am.keys() {
if !bm.contains_key(k) {
out.push(Change::RemoveAttr {
path: path.to_vec(),
key: k.clone(),
});
}
}
}
fn diff_extra(
am: &Map<String, Value>,
bm: &Map<String, Value>,
path: &mut [usize],
out: &mut Vec<Change>,
) {
for (k, v) in bm {
if am.get(k) != Some(v) {
out.push(Change::SetExtra {
path: path.to_vec(),
key: k.clone(),
value: v.clone(),
});
}
}
for k in am.keys() {
if !bm.contains_key(k) {
out.push(Change::RemoveExtra {
path: path.to_vec(),
key: k.clone(),
});
}
}
}
enum Step {
Match,
Del(usize),
Ins(usize),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Key {
Orig(usize),
New(usize),
}
enum Role {
Modify(usize),
Replace(usize),
}
fn diff_children(a: &[Node], b: &[Node], path: &mut Vec<usize>, out: &mut Vec<Change>) {
let mut start = 0;
while start < a.len() && start < b.len() && a[start] == b[start] {
start += 1;
}
let mut ea = a.len();
let mut eb = b.len();
while ea > start && eb > start && a[ea - 1] == b[eb - 1] {
ea -= 1;
eb -= 1;
}
let am = &a[start..ea];
let bm = &b[start..eb];
if am.is_empty() && bm.is_empty() {
return;
}
let steps = lcs_align(am, bm);
let mut gaps: Vec<(Vec<usize>, Vec<usize>)> = Vec::new();
let mut cur_dels: Vec<usize> = Vec::new();
let mut cur_inss: Vec<usize> = Vec::new();
let mut bm_match_src: HashMap<usize, usize> = HashMap::new(); let (mut ai, mut bj) = (0usize, 0usize);
for step in &steps {
match step {
Step::Match => {
gaps.push((std::mem::take(&mut cur_dels), std::mem::take(&mut cur_inss)));
bm_match_src.insert(bj, ai);
ai += 1;
bj += 1;
}
Step::Del(i) => {
cur_dels.push(*i);
ai += 1;
}
Step::Ins(j) => {
cur_inss.push(*j);
bj += 1;
}
}
}
gaps.push((cur_dels, cur_inss));
let all_dels: Vec<usize> = gaps.iter().flat_map(|(d, _)| d.iter().copied()).collect();
let mut del_used = vec![false; all_dels.len()];
let mut bm_move_src: HashMap<usize, usize> = HashMap::new(); let mut am_moved: HashSet<usize> = HashSet::new();
for (_, inss) in &gaps {
for &j in inss {
if let Some(slot) = all_dels
.iter()
.enumerate()
.find(|(slot, &d)| !del_used[*slot] && am[d] == bm[j])
.map(|(slot, _)| slot)
{
del_used[slot] = true;
bm_move_src.insert(j, all_dels[slot]);
am_moved.insert(all_dels[slot]);
}
}
}
let mut bm_role: HashMap<usize, Role> = HashMap::new();
let mut removed_am: Vec<usize> = Vec::new();
for (dels, inss) in &gaps {
let rem_dels: Vec<usize> = dels
.iter()
.copied()
.filter(|d| !am_moved.contains(d))
.collect();
let rem_inss: Vec<usize> = inss
.iter()
.copied()
.filter(|j| !bm_move_src.contains_key(j))
.collect();
let pairs = rem_dels.len().min(rem_inss.len());
for k in 0..pairs {
let (d, j) = (rem_dels[k], rem_inss[k]);
if am[d].node_type == bm[j].node_type {
bm_role.insert(j, Role::Modify(d)); } else {
bm_role.insert(j, Role::Replace(d)); }
}
removed_am.extend_from_slice(&rem_dels[pairs..]);
}
let mut target: Vec<Key> = Vec::with_capacity(bm.len());
for j in 0..bm.len() {
let key = if let Some(&i) = bm_match_src.get(&j) {
Key::Orig(i)
} else if let Some(&d) = bm_move_src.get(&j) {
Key::Orig(d)
} else {
match bm_role.get(&j) {
Some(Role::Modify(d) | Role::Replace(d)) => Key::Orig(*d),
None => Key::New(j),
}
};
target.push(key);
}
let target_pos: HashMap<Key, usize> = target.iter().enumerate().map(|(i, &k)| (k, i)).collect();
let is_moved = |k: Key| matches!(k, Key::Orig(x) if am_moved.contains(&x));
let mut live: Vec<Key> = (0..am.len()).map(Key::Orig).collect();
for &d in &removed_am {
let idx = live.iter().position(|&k| k == Key::Orig(d)).unwrap();
out.push(Change::Remove {
path: path.clone(),
index: start + idx,
});
live.remove(idx);
}
let mut p = 0;
while p < target.len() {
let want = target[p];
if live.get(p).copied() == Some(want) {
p += 1;
continue;
}
if let Key::New(j) = want {
out.push(Change::Insert {
path: path.clone(),
index: start + p,
node: bm[j].clone(),
});
live.insert(p, want);
p += 1;
continue;
}
let cur = live[p];
let mover = if is_moved(cur) { cur } else { want };
let from = live.iter().position(|&k| k == mover).unwrap();
let tmover = target_pos[&mover];
let dest = live
.iter()
.filter(|&&k| k != mover && target_pos[&k] < tmover)
.count();
out.push(Change::Move {
path: path.clone(),
from: start + from,
to: start + dest,
});
let k = live.remove(from);
live.insert(dest, k);
}
debug_assert_eq!(live, target, "diff move simulation diverged from target");
for (j, target_node) in bm.iter().enumerate() {
match bm_role.get(&j) {
Some(Role::Modify(d)) => {
path.push(start + j);
diff_node(&am[*d], target_node, path, out);
path.pop();
}
Some(Role::Replace(_)) => {
let mut child = path.clone();
child.push(start + j);
out.push(Change::Replace {
path: child,
node: target_node.clone(),
});
}
None => {}
}
}
}
fn lcs_align(a: &[Node], b: &[Node]) -> Vec<Step> {
let (m, n) = (a.len(), b.len());
if m == 0 {
return (0..n).map(Step::Ins).collect();
}
if n == 0 {
return (0..m).map(Step::Del).collect();
}
let mut dp = vec![vec![0u32; n + 1]; m + 1];
for i in (0..m).rev() {
for j in (0..n).rev() {
dp[i][j] = if a[i] == b[j] {
dp[i + 1][j + 1] + 1
} else {
dp[i + 1][j].max(dp[i][j + 1])
};
}
}
let mut steps = Vec::with_capacity(m.max(n));
let (mut i, mut j) = (0usize, 0usize);
while i < m && j < n {
if a[i] == b[j] {
steps.push(Step::Match);
i += 1;
j += 1;
} else if dp[i + 1][j] >= dp[i][j + 1] {
steps.push(Step::Del(i));
i += 1;
} else {
steps.push(Step::Ins(j));
j += 1;
}
}
while i < m {
steps.push(Step::Del(i));
i += 1;
}
while j < n {
steps.push(Step::Ins(j));
j += 1;
}
steps
}
fn node_at_mut<'a>(
root: &'a mut Node,
path: &[usize],
) -> std::result::Result<&'a mut Node, ApplyError> {
root.node_at_mut(path).ok_or_else(|| ApplyError {
path: path.to_vec(),
})
}
fn apply_one(root: &mut Node, change: &Change) -> std::result::Result<(), ApplyError> {
match change {
Change::SetAttr { path, key, value } => {
node_at_mut(root, path)?.set_attr(key.clone(), value.clone());
}
Change::RemoveAttr { path, key } => {
node_at_mut(root, path)?.remove_attr(key);
}
Change::SetText { path, text } => {
node_at_mut(root, path)?.text = text.clone();
}
Change::SetMarks { path, marks } => {
node_at_mut(root, path)?.marks = marks.clone();
}
Change::SetExtra { path, key, value } => {
node_at_mut(root, path)?
.extra
.insert(key.clone(), value.clone());
}
Change::RemoveExtra { path, key } => {
node_at_mut(root, path)?.extra.remove(key);
}
Change::Insert { path, index, node } => {
let parent = node_at_mut(root, path)?;
if *index > parent.child_count() {
let mut p = path.clone();
p.push(*index);
return Err(ApplyError { path: p });
}
parent.insert_child(*index, node.clone());
}
Change::Remove { path, index } => {
if node_at_mut(root, path)?.remove_child(*index).is_none() {
let mut p = path.clone();
p.push(*index);
return Err(ApplyError { path: p });
}
}
Change::Replace { path, node } => {
if path.is_empty() {
*root = node.clone();
} else {
let (parent_path, last) = path.split_at(path.len() - 1);
let parent = node_at_mut(root, parent_path)?;
if parent.replace_child(last[0], node.clone()).is_none() {
return Err(ApplyError { path: path.clone() });
}
}
}
Change::Move { path, from, to } => {
let parent = node_at_mut(root, path)?;
let len = parent.child_count();
if *from >= len || *to >= len {
let mut p = path.clone();
p.push(if *from >= len { *from } else { *to });
return Err(ApplyError { path: p });
}
let node = parent.remove_child(*from).expect("from bounds-checked");
parent.insert_child(*to, node);
}
}
Ok(())
}