#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalizedText {
pub value: String,
pub language: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VocabularyHeader {
pub iri: String,
pub name: Option<String>,
pub labels: Vec<LocalizedText>,
pub comments: Vec<LocalizedText>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Vocabulary {
pub header: VocabularyHeader,
pub node_count: Option<usize>,
pub depth: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VocabularyNode {
pub header: VocabularyHeader,
pub position: i32,
pub children: Vec<VocabularyNode>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VocabularyTree {
pub root: VocabularyHeader,
pub children: Vec<VocabularyNode>,
pub project_iri: String,
pub requested_node: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VocabularyDetail {
pub tree: VocabularyTree,
pub subtree_of: Option<String>,
pub node_count: usize,
pub depth: usize,
}
impl VocabularyTree {
pub fn count_and_depth(&self, from: Option<&str>) -> (usize, usize) {
match from {
None => {
let mut count = 0;
let mut depth = 0;
for child in &self.children {
let (child_count, child_height) = subtree_stats(child);
count += child_count;
depth = depth.max(child_height);
}
(count, depth)
}
Some(iri) => match find_node(&self.children, iri) {
Some(node) => subtree_stats(node),
None => (0, 0),
},
}
}
}
fn subtree_stats(node: &VocabularyNode) -> (usize, usize) {
let mut count = 1;
let mut max_child_height = 0;
for child in &node.children {
let (child_count, child_height) = subtree_stats(child);
count += child_count;
max_child_height = max_child_height.max(child_height);
}
(count, 1 + max_child_height)
}
fn find_node<'a>(nodes: &'a [VocabularyNode], iri: &str) -> Option<&'a VocabularyNode> {
for node in nodes {
if node.header.iri == iri {
return Some(node);
}
if let Some(found) = find_node(&node.children, iri) {
return Some(found);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn header(iri: &str) -> VocabularyHeader {
VocabularyHeader {
iri: iri.to_string(),
name: Some(iri.to_string()),
labels: vec![LocalizedText {
value: iri.to_string(),
language: Some("en".into()),
}],
comments: vec![],
}
}
fn leaf(iri: &str, position: i32) -> VocabularyNode {
VocabularyNode {
header: header(iri),
position,
children: vec![],
}
}
fn fixture_tree() -> VocabularyTree {
let node2b1 = leaf("node2b1", 0);
let node2b = VocabularyNode {
header: header("node2b"),
position: 1,
children: vec![node2b1],
};
let node2a = leaf("node2a", 0);
let node2 = VocabularyNode {
header: header("node2"),
position: 1,
children: vec![node2a, node2b],
};
let node1 = leaf("node1", 0);
VocabularyTree {
root: header("root"),
children: vec![node1, node2],
project_iri: "http://rdfh.ch/projects/0001".into(),
requested_node: None,
}
}
#[test]
fn count_and_depth_whole_tree() {
let tree = fixture_tree();
let (count, depth) = tree.count_and_depth(None);
assert_eq!(count, 5);
assert_eq!(depth, 3);
}
#[test]
fn count_and_depth_subtree_of_non_root_branch() {
let tree = fixture_tree();
let (count, depth) = tree.count_and_depth(Some("node2"));
assert_eq!(count, 4);
assert_eq!(depth, 3);
}
#[test]
fn count_and_depth_subtree_of_leaf_yields_one_node_one_level() {
let tree = fixture_tree();
let (count, depth) = tree.count_and_depth(Some("node2a"));
assert_eq!(count, 1);
assert_eq!(depth, 1);
}
#[test]
fn count_and_depth_subtree_of_intermediate_branch() {
let tree = fixture_tree();
let (count, depth) = tree.count_and_depth(Some("node2b"));
assert_eq!(count, 2);
assert_eq!(depth, 2);
}
#[test]
fn count_and_depth_unknown_iri_returns_zero() {
let tree = fixture_tree();
let (count, depth) = tree.count_and_depth(Some("does-not-exist"));
assert_eq!(count, 0);
assert_eq!(depth, 0);
}
#[test]
fn position_ordering_is_preserved_as_given() {
let children = vec![leaf("a", 0), leaf("b", 1), leaf("c", 2)];
let parent = VocabularyNode {
header: header("parent"),
position: 0,
children,
};
assert_eq!(parent.children[0].header.iri, "a");
assert_eq!(parent.children[0].position, 0);
assert_eq!(parent.children[1].header.iri, "b");
assert_eq!(parent.children[1].position, 1);
assert_eq!(parent.children[2].header.iri, "c");
assert_eq!(parent.children[2].position, 2);
}
#[test]
fn d15_node_count_matches_manually_counted_rendered_rows() {
let tree = fixture_tree();
let whole_vocabulary_rows = ["node1", "node2", "node2a", "node2b", "node2b1"];
let (whole_count, _) = tree.count_and_depth(None);
assert_eq!(whole_count, whole_vocabulary_rows.len());
let subtree_rows = ["node2", "node2a", "node2b", "node2b1"];
let (subtree_count, _) = tree.count_and_depth(Some("node2"));
assert_eq!(subtree_count, subtree_rows.len());
let leaf_rows = ["node2a"];
let (leaf_count, _) = tree.count_and_depth(Some("node2a"));
assert_eq!(leaf_count, leaf_rows.len());
}
#[test]
fn localized_text_construction_and_equality() {
let a = LocalizedText {
value: "Period".into(),
language: Some("en".into()),
};
let b = a.clone();
assert_eq!(a, b);
assert_eq!(a.value, "Period");
assert_eq!(a.language.as_deref(), Some("en"));
}
#[test]
fn localized_text_untagged() {
let a = LocalizedText {
value: "untagged".into(),
language: None,
};
assert_eq!(a.language, None);
}
#[test]
fn vocabulary_node_count_none_before_count_flag() {
let vocab = Vocabulary {
header: header("vocab"),
node_count: None,
depth: None,
};
assert_eq!(vocab.node_count, None);
assert_eq!(vocab.depth, None);
}
#[test]
fn vocabulary_tree_requested_node_default_none() {
let tree = fixture_tree();
assert_eq!(tree.requested_node, None);
assert_eq!(tree.project_iri, "http://rdfh.ch/projects/0001");
}
#[test]
fn vocabulary_detail_construction_and_equality() {
let tree = fixture_tree();
let (node_count, depth) = tree.count_and_depth(None);
let detail = VocabularyDetail {
tree: tree.clone(),
subtree_of: None,
node_count,
depth,
};
let cloned = detail.clone();
assert_eq!(detail, cloned);
assert_eq!(detail.node_count, 5);
assert_eq!(detail.depth, 3);
assert_eq!(detail.subtree_of, None);
}
}