natural-xml-diff 0.2.0

Natural diffing between XML documents
Documentation
use xot::Xot;

use crate::comparison::Comparison;
use crate::partition::equal_pairs;
use crate::vtree::{Status, Vnode, Vtree};

impl Comparison {
    pub(crate) fn diff_status(&mut self, xot: &Xot) {
        let overlaps = self.partition();
        let mut equal_pairs = equal_pairs(&overlaps);
        self.update_status(&equal_pairs, Status::Equal);
        let update_pairs = self.find_updates(xot, &overlaps);
        self.update_status(&update_pairs, Status::Update);
        equal_pairs.extend(update_pairs);
        equal_pairs.extend(self.propagate(xot, &equal_pairs));
        self.propagation_consistency(&equal_pairs);

        // we may have stray nodes that are considered equal or update but have
        // a different ancestor. We consider them different after all otherwise
        // they would be lost once their parent is deleted/inserted.
        self.make_stray_different();

        // but the root is always an update of the other root
        self.vtree_a.nodes[0].status = Status::Update(0);
        self.vtree_b.nodes[0].status = Status::Update(0);
    }

    fn make_stray_different(&mut self) {
        make_stray_different(&mut self.vtree_a, &mut self.vtree_b);
        make_stray_different(&mut self.vtree_b, &mut self.vtree_a);
    }
}

fn make_stray_different(vtree: &mut Vtree, other_vtree: &mut Vtree) {
    let mut to_update = Vec::new();
    for (index, vnode) in vtree.nodes.iter().enumerate() {
        if vnode.status != Status::Different && has_different_ancestor(vnode, vtree) {
            to_update.push(index);
            match vnode.status {
                // also adjust the other tree
                Status::Equal(id) | Status::Update(id) => {
                    other_vtree.nodes[id as usize].status = Status::Different;
                }
                _ => {}
            }
        }
    }
    for index in to_update {
        vtree.nodes[index].status = Status::Different;
    }
}

fn has_different_ancestor(vnode: &Vnode, vtree: &Vtree) -> bool {
    let mut parent_id = vnode.parent_id;

    while let Some(current_parent_id) = parent_id {
        if vtree.nodes[current_parent_id].status == Status::Different {
            return true;
        }
        parent_id = vtree.nodes[current_parent_id].parent_id;
    }
    false
}

#[cfg(test)]
mod test {
    use super::*;
    use xot::Xot;

    #[test]
    fn test_diff_status_simple() {
        let mut xot = Xot::new();
        let doc_a = xot
            .parse("<container><a/><b/><x/><c/><d/></container>")
            .unwrap();
        let doc_b = xot
            .parse("<container><x/><a/><b/><c/><d/></container>")
            .unwrap();

        let mut comparison = Comparison::new(&xot, doc_a, doc_b);
        comparison.diff_status(&xot);
        assert_eq!(comparison.vtree_a.nodes[1].status, Status::Equal(1));
        assert_eq!(comparison.vtree_a.nodes[2].status, Status::Equal(3));
        assert_eq!(comparison.vtree_a.nodes[3].status, Status::Equal(4));
        assert_eq!(comparison.vtree_a.nodes[5].status, Status::Equal(5));
        assert_eq!(comparison.vtree_a.nodes[6].status, Status::Equal(6));
    }
}