use std::ops::Range;
pub type NodeId = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XPathNodeKind {
Document,
Element,
Attribute,
Text,
Comment,
CData,
PI,
Namespace,
}
pub trait DocIndexLike {
fn graft_dynamic_document(
&self,
_doc: &sup_xml_tree::dom::Document,
) -> Option<NodeId> {
None
}
fn children(&self, id: NodeId) -> &[NodeId];
fn parent(&self, id: NodeId) -> Option<NodeId>;
fn attr_range(&self, id: NodeId) -> Range<NodeId>;
fn ns_range(&self, _id: NodeId) -> Range<NodeId> { 0..0 }
fn kind(&self, id: NodeId) -> XPathNodeKind;
fn pi_target(&self, id: NodeId) -> &str;
fn string_value(&self, id: NodeId) -> String;
fn node_name(&self, id: NodeId) -> &str;
fn local_name(&self, id: NodeId) -> &str;
fn namespace_uri(&self, id: NodeId) -> &str;
fn namespace_prefix(&self, _id: NodeId) -> Option<&str> { None }
fn is_element(&self, id: NodeId) -> bool {
matches!(self.kind(id), XPathNodeKind::Element)
}
fn is_id_attribute(&self, attr_id: NodeId) -> bool {
if !matches!(self.kind(attr_id), XPathNodeKind::Attribute) { return false; }
let n = self.node_name(attr_id);
n == "xml:id" || self.local_name(attr_id) == "id"
}
fn is_idref_attribute(&self, _attr_id: NodeId) -> bool { false }
fn allocate_rtf_text_nodes(&self, _values: Vec<String>) -> Option<Vec<NodeId>> { None }
fn rtf_builder(&self) -> Option<super::rtf::RtfBuilder> { None }
fn finish_rtf(&self, _builder: super::rtf::RtfBuilder) -> Option<NodeId> { None }
fn rtf_node_type(&self, _id: NodeId) -> Option<(String, String)> { None }
fn is_synthetic_wrap(&self, _id: NodeId) -> bool { false }
}
#[cfg(test)]
mod tests {
use super::*;
struct StubIndex;
impl DocIndexLike for StubIndex {
fn children(&self, id: NodeId) -> &[NodeId] {
match id {
0 => &[1],
1 => &[2],
_ => &[],
}
}
fn parent(&self, id: NodeId) -> Option<NodeId> {
match id {
0 => None,
1 => Some(0),
2 => Some(1),
_ => None,
}
}
fn attr_range(&self, _id: NodeId) -> Range<NodeId> { 0..0 }
fn kind(&self, id: NodeId) -> XPathNodeKind {
match id {
0 => XPathNodeKind::Document,
1 => XPathNodeKind::Element,
2 => XPathNodeKind::Text,
_ => XPathNodeKind::Text,
}
}
fn pi_target(&self, _id: NodeId) -> &str { "" }
fn string_value(&self, _id: NodeId) -> String { String::new() }
fn node_name(&self, _id: NodeId) -> &str { "" }
fn local_name(&self, _id: NodeId) -> &str { "" }
fn namespace_uri(&self, _id: NodeId) -> &str { "" }
}
#[test]
fn default_ns_range_is_empty() {
let idx = StubIndex;
for id in 0..=2 {
let r = idx.ns_range(id);
assert_eq!(r, 0..0, "ns_range({id}) should default to empty");
assert!(r.is_empty());
}
}
#[test]
fn default_namespace_prefix_is_none() {
let idx = StubIndex;
for id in 0..=2 {
assert_eq!(idx.namespace_prefix(id), None);
}
}
#[test]
fn default_is_element_matches_kind() {
let idx = StubIndex;
assert!(!idx.is_element(0)); assert!( idx.is_element(1)); assert!(!idx.is_element(2)); }
#[test]
fn stub_required_methods_smoke() {
let idx = StubIndex;
assert_eq!(idx.children(0), &[1]);
assert_eq!(idx.children(1), &[2]);
assert!(idx.children(2).is_empty());
assert_eq!(idx.parent(0), None);
assert_eq!(idx.parent(1), Some(0));
assert_eq!(idx.parent(2), Some(1));
assert_eq!(idx.parent(99), None);
assert!(idx.attr_range(1).is_empty());
assert_eq!(idx.kind(0), XPathNodeKind::Document);
assert_eq!(idx.kind(1), XPathNodeKind::Element);
assert_eq!(idx.kind(2), XPathNodeKind::Text);
assert_eq!(idx.kind(99), XPathNodeKind::Text);
assert_eq!(idx.pi_target(0), "");
assert_eq!(idx.string_value(0), "");
assert_eq!(idx.node_name(0), "");
assert_eq!(idx.local_name(0), "");
assert_eq!(idx.namespace_uri(0), "");
}
#[test]
fn xpath_node_kind_traits() {
let k = XPathNodeKind::Element;
let copy = k; let clone = k.clone(); assert_eq!(k, copy); assert_eq!(k, clone);
assert_ne!(k, XPathNodeKind::Text);
let s = format!("{:?}", k);
assert!(s.contains("Element"));
let variants = [
XPathNodeKind::Document,
XPathNodeKind::Element,
XPathNodeKind::Attribute,
XPathNodeKind::Text,
XPathNodeKind::Comment,
XPathNodeKind::CData,
XPathNodeKind::PI,
XPathNodeKind::Namespace,
];
for (i, a) in variants.iter().enumerate() {
for (j, b) in variants.iter().enumerate() {
assert_eq!(i == j, a == b);
}
}
}
}