use crate::bitflags_like;
use crate::dom::Dom;
use crate::node_id::NodeId;
bitflags_like! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DocumentPosition(u16) {
DISCONNECTED = 0b0000_0001;
PRECEDING = 0b0000_0010;
FOLLOWING = 0b0000_0100;
CONTAINS = 0b0000_1000;
CONTAINED_BY = 0b0001_0000;
IMPLEMENTATION_SPECIFIC = 0b0010_0000;
}
}
impl<Ext> Dom<Ext> {
pub fn compare_document_position(&self, a: NodeId, b: NodeId) -> DocumentPosition {
if a == b {
return DocumentPosition::empty();
}
let a_path = self.ancestor_path(a);
let b_path = self.ancestor_path(b);
match (a_path.first(), b_path.first()) {
(Some(&ra), Some(&rb)) if ra != rb => {
return DocumentPosition::DISCONNECTED
| DocumentPosition::IMPLEMENTATION_SPECIFIC
| DocumentPosition::PRECEDING;
}
(None, _) | (_, None) => {
return DocumentPosition::DISCONNECTED
| DocumentPosition::IMPLEMENTATION_SPECIFIC
| DocumentPosition::PRECEDING;
}
_ => {}
}
let mut common = 0;
while common < a_path.len() && common < b_path.len() && a_path[common] == b_path[common] {
common += 1;
}
if common == a_path.len() && common < b_path.len() {
return DocumentPosition::CONTAINS | DocumentPosition::PRECEDING;
}
if common == b_path.len() && common < a_path.len() {
return DocumentPosition::CONTAINED_BY | DocumentPosition::FOLLOWING;
}
debug_assert!(common > 0, "compare_document_position: no LCA found");
let lca = a_path[common - 1];
let a_branch = a_path[common];
let b_branch = b_path[common];
let mut cur = self.get_node(lca).and_then(|n| n.first_child);
while let Some(c) = cur {
if c == a_branch {
return DocumentPosition::FOLLOWING;
}
if c == b_branch {
return DocumentPosition::PRECEDING;
}
cur = self.get_node(c).and_then(|n| n.next_sibling);
}
DocumentPosition::empty()
}
pub fn is_equal_node(&self, a: NodeId, b: NodeId) -> bool {
use crate::node::NodeData;
let Some(na) = self.get_node(a) else {
return false;
};
let Some(nb) = self.get_node(b) else {
return false;
};
match (&na.data, &nb.data) {
(
NodeData::Element {
tag: ta,
attrs: aa,
classes: ca,
..
},
NodeData::Element {
tag: tb,
attrs: ab,
classes: cb,
..
},
) => {
if ta != tb || aa != ab || ca != cb {
return false;
}
}
(NodeData::Text { data: da }, NodeData::Text { data: db }) => {
return da == db;
}
(NodeData::Comment { data: da }, NodeData::Comment { data: db }) => {
return da == db;
}
(NodeData::Fragment, NodeData::Fragment) => {}
_ => return false,
}
let mut ca = na.first_child;
let mut cb = nb.first_child;
loop {
match (ca, cb) {
(None, None) => return true,
(Some(ca_id), Some(cb_id)) => {
if !self.is_equal_node(ca_id, cb_id) {
return false;
}
ca = self.get_node(ca_id).and_then(|n| n.next_sibling);
cb = self.get_node(cb_id).and_then(|n| n.next_sibling);
}
_ => return false,
}
}
}
pub fn ancestor_path(&self, id: NodeId) -> Vec<NodeId> {
let mut path = Vec::new();
let mut cur = Some(id);
while let Some(c) = cur {
if self.get_node(c).is_none() {
return Vec::new();
}
path.push(c);
cur = self.get_node(c).and_then(|n| n.parent);
}
path.reverse();
path
}
pub fn common_ancestor(&self, a: NodeId, b: NodeId) -> Option<NodeId> {
if a == b {
return self.get_node(a).map(|_| a);
}
let a_path = self.ancestor_path(a);
let b_path = self.ancestor_path(b);
match (a_path.first(), b_path.first()) {
(Some(ra), Some(rb)) if ra != rb => return None,
(None, _) | (_, None) => return None,
_ => {}
}
let mut last = None;
for (x, y) in a_path.iter().zip(b_path.iter()) {
if x == y {
last = Some(*x);
} else {
break;
}
}
last
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! bitflags_like {
(
$(#[$outer:meta])*
$vis:vis struct $name:ident ( $repr:ty ) {
$( $flag:ident = $value:expr; )+
}
) => {
$(#[$outer])*
$vis struct $name($repr);
impl $name {
$( pub const $flag: Self = Self($value); )+
#[inline] pub const fn empty() -> Self { Self(0) }
#[inline] pub const fn all() -> Self { Self( $( $value )|+ ) }
#[inline] pub const fn bits(self) -> $repr { self.0 }
#[inline] pub const fn from_bits_truncate(bits: $repr) -> Self {
Self(bits & Self::all().0)
}
#[inline] pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
#[inline] pub const fn is_empty(self) -> bool { self.0 == 0 }
#[inline] pub const fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
}
impl std::ops::BitOr for $name {
type Output = Self;
#[inline] fn bitor(self, rhs: Self) -> Self { Self(self.0 | rhs.0) }
}
impl std::ops::BitOrAssign for $name {
#[inline] fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0; }
}
impl std::ops::BitAnd for $name {
type Output = Self;
#[inline] fn bitand(self, rhs: Self) -> Self { Self(self.0 & rhs.0) }
}
impl std::ops::BitAndAssign for $name {
#[inline] fn bitand_assign(&mut self, rhs: Self) { self.0 &= rhs.0; }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Dom;
fn build() -> (Dom, NodeId, NodeId, NodeId, NodeId) {
let mut dom: Dom = Dom::new();
let root = dom.root();
let a = dom.create_element("a");
let b = dom.create_element("b");
let grandchild = dom.create_element("g");
dom.append_child(root, a).unwrap();
dom.append_child(root, b).unwrap();
dom.append_child(a, grandchild).unwrap();
(dom, a, b, grandchild, root)
}
#[test]
fn self_is_empty() {
let (dom, a, _, _, _) = build();
assert_eq!(
dom.compare_document_position(a, a),
DocumentPosition::empty()
);
}
#[test]
fn ancestor_contains_descendant() {
let (dom, a, _, g, _) = build();
let r = dom.compare_document_position(a, g);
assert!(r.contains(DocumentPosition::CONTAINS));
assert!(r.contains(DocumentPosition::PRECEDING));
}
#[test]
fn descendant_contained_by_ancestor() {
let (dom, a, _, g, _) = build();
let r = dom.compare_document_position(g, a);
assert!(r.contains(DocumentPosition::CONTAINED_BY));
assert!(r.contains(DocumentPosition::FOLLOWING));
}
#[test]
fn siblings_ordered_by_position() {
let (dom, a, b, _, _) = build();
assert!(
dom.compare_document_position(a, b)
.contains(DocumentPosition::FOLLOWING)
);
assert!(
dom.compare_document_position(b, a)
.contains(DocumentPosition::PRECEDING)
);
}
#[test]
fn disconnected_nodes_flagged() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("a"); let b = dom.create_element("b"); let r = dom.compare_document_position(a, b);
assert!(r.contains(DocumentPosition::DISCONNECTED));
}
#[test]
fn common_ancestor_self_is_self() {
let (dom, a, _, _, _) = build();
assert_eq!(dom.common_ancestor(a, a), Some(a));
}
#[test]
fn common_ancestor_siblings_is_parent() {
let (dom, a, b, _, root) = build();
assert_eq!(dom.common_ancestor(a, b), Some(root));
}
#[test]
fn common_ancestor_nested_is_ancestor() {
let (dom, a, _, g, _) = build();
assert_eq!(dom.common_ancestor(a, g), Some(a));
assert_eq!(dom.common_ancestor(g, a), Some(a));
}
#[test]
fn common_ancestor_cousins_is_lca() {
let (dom, _, b, g, root) = build();
assert_eq!(dom.common_ancestor(g, b), Some(root));
}
#[test]
fn common_ancestor_disconnected_returns_none() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("a"); let b = dom.create_element("b"); assert_eq!(dom.common_ancestor(a, b), None);
}
#[test]
fn equal_node_same_tag_and_attrs() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("div");
let b = dom.create_element("div");
dom.set_attribute(a, "role", "banner").unwrap();
dom.set_attribute(b, "role", "banner").unwrap();
assert!(dom.is_equal_node(a, b));
}
#[test]
fn unequal_different_tag() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("div");
let b = dom.create_element("span");
assert!(!dom.is_equal_node(a, b));
}
#[test]
fn unequal_different_attr() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("div");
let b = dom.create_element("div");
dom.set_attribute(a, "role", "banner").unwrap();
dom.set_attribute(b, "role", "navigation").unwrap();
assert!(!dom.is_equal_node(a, b));
}
#[test]
fn equal_text_nodes_same_data() {
let mut dom: Dom = Dom::new();
let a = dom.create_text_node("hi");
let b = dom.create_text_node("hi");
let c = dom.create_text_node("bye");
assert!(dom.is_equal_node(a, b));
assert!(!dom.is_equal_node(a, c));
}
#[test]
fn equal_with_children() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("div");
let a1 = dom.create_text_node("hello");
dom.append_child(a, a1).unwrap();
let b = dom.create_element("div");
let b1 = dom.create_text_node("hello");
dom.append_child(b, b1).unwrap();
assert!(dom.is_equal_node(a, b));
}
#[test]
fn unequal_different_child_count() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("div");
let a1 = dom.create_text_node("x");
dom.append_child(a, a1).unwrap();
let b = dom.create_element("div");
assert!(!dom.is_equal_node(a, b));
}
#[test]
fn unequal_different_node_types() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("div");
let b = dom.create_text_node("div");
assert!(!dom.is_equal_node(a, b));
}
}