use crate::{
Result, Rudof,
errors::DataError,
formats::{IriNormalizationMode, NodeInspectionMode},
};
use prefixmap::IriRef;
use rudof_iri::IriS;
use rudof_rdf::rdf_core::{NeighsRDF, query::QueryRDF};
use shex_ast::{ShapeMapParser, shapemap::NodeSelector};
use std::{collections::HashMap, fmt::Debug, io};
use termtree::Tree;
pub fn show_node_info<W: io::Write>(
rudof: &mut Rudof,
node: &str,
predicates: Option<&[String]>,
show_node_mode: Option<&NodeInspectionMode>,
depth: Option<usize>,
show_hyperlinks: Option<bool>,
show_colors: Option<bool>,
iri_mode: IriNormalizationMode,
writer: &mut W,
) -> Result<()> {
let config = NodeDisplayConfig::from_options(predicates, show_node_mode, depth, show_hyperlinks, show_colors);
let data = rudof.data.as_mut().ok_or(Box::new(DataError::NoRdfDataLoaded))?;
if !data.is_rdf() {
return Err(Box::new(DataError::NoRdfDataLoaded).into());
}
let node_selector = parse_node_selector(node, iri_mode)?;
let node_infos = collect_node_information(data.unwrap_rdf_mut(), node_selector, &config)?;
write_node_information_list(&node_infos, data.unwrap_rdf_mut(), writer, &config)?;
Ok(())
}
fn collect_node_information<R>(
rdf: &R,
node_selector: NodeSelector,
config: &NodeDisplayConfig,
) -> Result<Vec<NodeInfo<R>>>
where
R: NeighsRDF + Debug + QueryRDF,
{
let nodes = node_selector
.nodes(rdf)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?;
nodes.iter().map(|node| build_node_info(rdf, node, config)).collect()
}
fn build_node_info<R>(rdf: &R, node: &R::Term, config: &NodeDisplayConfig) -> Result<NodeInfo<R>>
where
R: NeighsRDF + Debug + QueryRDF,
{
let subject =
R::term_as_subject(node).map_err(|e| Box::new(DataError::FailedQualification { error: e.to_string() }))?;
let subject_qualified = qualify_subject(rdf, &subject, config.show_colors)?;
let outgoing = if config.mode.show_outgoing() {
collect_outgoing_arcs_recursive(rdf, &subject, &config.predicates, config.depth)?
} else {
HashMap::new()
};
let incoming = if config.mode.show_incoming() {
collect_incoming_arcs_recursive(rdf, &subject, config.depth)?
} else {
HashMap::new()
};
Ok(NodeInfo {
subject,
subject_qualified,
outgoing,
incoming,
})
}
fn collect_outgoing_arcs_recursive<S: NeighsRDF>(
rdf: &S,
subject: &S::Subject,
predicates: &[String],
depth: usize,
) -> Result<HashMap<S::IRI, Vec<OutgoingNeighsNode<S>>>> {
if depth == 1 {
return Ok(collect_outgoing_arcs(rdf, subject, predicates)?
.into_iter()
.map(|(predicate, terms)| {
let nodes = terms
.into_iter()
.map(|term| OutgoingNeighsNode::Term { term })
.collect();
(predicate, nodes)
})
.collect());
}
let arc_map = if predicates.is_empty() {
rdf.outgoing_arcs(subject)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?
} else {
let predicate_iris = convert_predicate_strings_to_iris(predicates, rdf)?;
let (map, _not_found) = rdf
.outgoing_arcs_from_list(subject, &predicate_iris)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?;
map
};
let mut result = HashMap::new();
for (predicate, objects) in arc_map {
let mut nodes = Vec::new();
for object in objects {
let nested_arcs = match S::term_as_subject(&object) {
Ok(obj_as_subject) => {
collect_outgoing_arcs_recursive(rdf, &obj_as_subject, predicates, depth - 1)?
},
Err(_) => {
HashMap::new()
},
};
nodes.push(OutgoingNeighsNode::More {
term: object,
rest: nested_arcs,
});
}
result.insert(predicate, nodes);
}
Ok(result)
}
fn collect_outgoing_arcs<S: NeighsRDF>(
rdf: &S,
subject: &S::Subject,
predicates: &[String],
) -> Result<HashMap<S::IRI, Vec<S::Term>>> {
let arc_map = if predicates.is_empty() {
rdf.outgoing_arcs(subject)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?
} else {
let predicate_iris = convert_predicate_strings_to_iris(predicates, rdf)?;
let (map, _not_found) = rdf
.outgoing_arcs_from_list(subject, &predicate_iris)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?;
map
};
Ok(arc_map
.into_iter()
.map(|(predicate, objects)| (predicate, objects.into_iter().collect()))
.collect())
}
fn collect_incoming_arcs_recursive<S: NeighsRDF>(
rdf: &S,
subject: &S::Subject,
depth: usize,
) -> Result<HashMap<S::IRI, Vec<IncomingNeighsNode<S>>>> {
if depth == 1 {
let incoming_arcs = collect_incoming_arcs(rdf, subject)?;
return Ok(incoming_arcs
.into_iter()
.map(|(predicate, subjects)| {
let nodes = subjects
.iter()
.map(|s| IncomingNeighsNode::Term {
term: S::subject_as_term(s),
})
.collect();
(predicate, nodes)
})
.collect());
}
let object: S::Term = subject.clone().into();
let arc_map = rdf
.incoming_arcs(&object)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?;
let mut result = HashMap::new();
for (predicate, subjects) in arc_map {
let mut nodes = Vec::new();
for subj in subjects {
let nested_arcs = collect_incoming_arcs_recursive(rdf, &subj, depth - 1)?;
nodes.push(IncomingNeighsNode::More {
term: S::subject_as_term(&subj),
rest: nested_arcs,
});
}
result.insert(predicate, nodes);
}
Ok(result)
}
fn collect_incoming_arcs<S: NeighsRDF>(rdf: &S, subject: &S::Subject) -> Result<HashMap<S::IRI, Vec<S::Subject>>> {
let object: S::Term = subject.clone().into();
let arc_map = rdf
.incoming_arcs(&object)
.map_err(|e| Box::new(DataError::FailedArcRetrieval { error: e.to_string() }))?;
Ok(arc_map
.into_iter()
.map(|(predicate, subjects)| (predicate, subjects.into_iter().collect()))
.collect())
}
fn write_node_information_list<S: NeighsRDF, W: io::Write>(
node_infos: &[NodeInfo<S>],
rdf: &S,
writer: &mut W,
config: &NodeDisplayConfig,
) -> Result<()> {
for node_info in node_infos {
write_node_information(node_info, rdf, writer, config)?;
}
Ok(())
}
fn write_node_information<S: NeighsRDF, W: io::Write>(
node_info: &NodeInfo<S>,
rdf: &S,
writer: &mut W,
config: &NodeDisplayConfig,
) -> Result<()> {
if config.mode.show_outgoing() && !node_info.outgoing.is_empty() {
writeln!(writer, "Outgoing arcs")
.map_err(|e| Box::new(DataError::FailedIoOperation { error: e.to_string() }))?;
let mut tree = Tree::new(node_info.subject_qualified.clone()).with_glyphs(create_outgoing_glyphs());
build_outgoing_tree(&mut tree, &node_info.outgoing, rdf, config)?;
writeln!(writer, "{}", tree).map_err(|e| Box::new(DataError::FailedIoOperation { error: e.to_string() }))?;
}
if config.mode.show_incoming() && !node_info.incoming.is_empty() {
writeln!(writer, "Incoming arcs")
.map_err(|e| Box::new(DataError::FailedIoOperation { error: e.to_string() }))?;
let subject_qualified = qualify_subject(rdf, &node_info.subject, config.show_colors)?;
let root_label = format!("{}\nâ–²", subject_qualified);
let mut tree = Tree::new(root_label).with_glyphs(create_incoming_glyphs());
build_incoming_tree(&mut tree, &node_info.incoming, rdf, config)?;
writeln!(writer, "{}", tree).map_err(|e| Box::new(DataError::FailedIoOperation { error: e.to_string() }))?;
}
Ok(())
}
fn build_outgoing_tree<S: NeighsRDF>(
tree: &mut Tree<String>,
outgoing_arcs: &HashMap<S::IRI, Vec<OutgoingNeighsNode<S>>>,
rdf: &S,
config: &NodeDisplayConfig,
) -> Result<()> {
let mut predicates: Vec<_> = outgoing_arcs.keys().collect();
predicates.sort();
for predicate in predicates {
let pred_str = qualify_iri(rdf, predicate, config.show_colors);
if let Some(objects) = outgoing_arcs.get(predicate) {
for object_node in objects {
match object_node {
OutgoingNeighsNode::Term { term } => {
let obj_str = qualify_term(rdf, term, config.show_colors)?;
let label = format!("─ {} ─► {}", pred_str, obj_str);
tree.leaves.push(Tree::new(label).with_glyphs(create_outgoing_glyphs()));
},
OutgoingNeighsNode::More { term, rest } => {
let obj_str = qualify_term(rdf, term, config.show_colors)?;
let label = format!("─ {} ─► {}", pred_str, obj_str);
let mut subtree = Tree::new(label).with_glyphs(create_outgoing_glyphs());
build_outgoing_tree(&mut subtree, rest, rdf, config)?;
tree.leaves.push(subtree);
},
}
}
}
}
Ok(())
}
fn build_incoming_tree<S: NeighsRDF>(
tree: &mut Tree<String>,
incoming_arcs: &HashMap<S::IRI, Vec<IncomingNeighsNode<S>>>,
rdf: &S,
config: &NodeDisplayConfig,
) -> Result<()> {
let mut predicates: Vec<_> = incoming_arcs.keys().collect();
predicates.sort();
for predicate in predicates {
let pred_str = qualify_iri(rdf, predicate, config.show_colors);
if let Some(subjects) = incoming_arcs.get(predicate) {
for subject_node in subjects {
match subject_node {
IncomingNeighsNode::Term { term } => {
let subj_str = qualify_term(rdf, term, config.show_colors)?;
let label = format!("─ {} ── {}", pred_str, subj_str);
tree.leaves.push(Tree::new(label).with_glyphs(create_incoming_glyphs()));
},
IncomingNeighsNode::More { term, rest } => {
let subj_str = qualify_term(rdf, term, config.show_colors)?;
let label = format!("─ {} ── {}", pred_str, subj_str);
let mut subtree = Tree::new(label).with_glyphs(create_incoming_glyphs());
build_incoming_tree(&mut subtree, rest, rdf, config)?;
tree.leaves.push(subtree);
},
}
}
}
}
Ok(())
}
fn create_incoming_glyphs() -> termtree::GlyphPalette {
termtree::GlyphPalette {
middle_item: "├",
last_item: "â””",
item_indent: "──",
middle_skip: "│",
last_skip: "",
skip_indent: " ",
}
}
fn create_outgoing_glyphs() -> termtree::GlyphPalette {
termtree::GlyphPalette {
middle_item: "├",
last_item: "â””",
item_indent: "──",
middle_skip: "│",
last_skip: "",
skip_indent: " ",
}
}
fn parse_node_selector(node_str: &str, iri_mode: IriNormalizationMode) -> Result<NodeSelector> {
let normalized = crate::utils::normalize_iri_str(node_str, iri_mode);
let node_str = normalized.as_str();
ShapeMapParser::parse_node_selector(node_str).map_err(|e| {
Box::new(DataError::FailedNodeSelectorParse {
node: node_str.to_string(),
error: e.to_string(),
})
.into()
})
}
fn parse_iri_ref(iri: &str) -> Result<IriRef> {
ShapeMapParser::parse_iri_ref(iri).map_err(|e| {
Box::new(DataError::FailedIriRefParse {
iri: iri.to_string(),
error: e.to_string(),
})
.into()
})
}
fn convert_predicate_strings_to_iris<S>(predicates: &[String], rdf: &S) -> Result<Vec<S::IRI>>
where
S: NeighsRDF,
{
predicates
.iter()
.map(|pred_str| {
let iri_ref = parse_iri_ref(pred_str)?;
let iri_s = match iri_ref {
IriRef::Prefixed { prefix, local } => {
rdf.resolve_prefix_local(prefix.as_str(), local.as_str()).map_err(|e| {
Box::new(DataError::FailedPrefixResolution {
prefix: prefix.to_string(),
error: e.to_string(),
})
})?
},
IriRef::Iri(iri) => iri,
};
Ok(iri_s.into())
})
.collect()
}
fn qualify_subject<S: NeighsRDF>(rdf: &S, subject: &S::Subject, show_colors: bool) -> Result<String> {
if show_colors {
Ok(rdf.qualify_subject(subject))
} else {
let prefixmap = rdf.prefixmap().unwrap_or_default().clone().without_colors();
let subject_term = S::subject_as_term(subject);
let node = S::term_as_object(&subject_term)
.map_err(|e| Box::new(DataError::FailedQualification { error: e.to_string() }))?;
Ok(node.show_qualified(&prefixmap))
}
}
fn qualify_iri<S: NeighsRDF>(rdf: &S, iri: &S::IRI, show_colors: bool) -> String {
if show_colors {
rdf.qualify_iri(iri)
} else {
let prefixmap = rdf.prefixmap().unwrap_or_default().clone().without_colors();
let iri_s: IriS = iri.clone().into();
prefixmap.qualify(&iri_s)
}
}
fn qualify_term<S: NeighsRDF>(rdf: &S, term: &S::Term, show_colors: bool) -> Result<String> {
if show_colors {
Ok(rdf.qualify_term(term))
} else {
let prefixmap = rdf.prefixmap().unwrap_or_default().clone().without_colors();
let node =
S::term_as_object(term).map_err(|e| Box::new(DataError::FailedQualification { error: e.to_string() }))?;
Ok(node.show_qualified(&prefixmap))
}
}
#[derive(Debug, Clone)]
struct NodeDisplayConfig {
predicates: Vec<String>,
mode: NodeInspectionMode,
depth: usize,
_show_hyperlinks: bool,
show_colors: bool,
}
impl NodeDisplayConfig {
fn from_options(
predicates: Option<&[String]>,
mode: Option<&NodeInspectionMode>,
depth: Option<usize>,
show_hyperlinks: Option<bool>,
show_colors: Option<bool>,
) -> Self {
Self {
predicates: predicates.map(|p| p.to_vec()).unwrap_or_default(),
mode: mode.copied().unwrap_or_default(),
depth: depth.unwrap_or(1),
_show_hyperlinks: show_hyperlinks.unwrap_or(false),
show_colors: show_colors.unwrap_or(true),
}
}
}
#[derive(Debug, Clone)]
pub struct NodeInfo<S: NeighsRDF> {
pub subject: S::Subject,
pub subject_qualified: String,
pub outgoing: HashMap<S::IRI, Vec<OutgoingNeighsNode<S>>>,
pub incoming: HashMap<S::IRI, Vec<IncomingNeighsNode<S>>>,
}
#[derive(Debug, Clone)]
pub enum OutgoingNeighsNode<S: NeighsRDF> {
Term { term: S::Term },
More {
term: S::Term,
rest: HashMap<S::IRI, Vec<OutgoingNeighsNode<S>>>,
},
}
#[derive(Debug, Clone)]
pub enum IncomingNeighsNode<S: NeighsRDF> {
Term { term: S::Term },
More {
term: S::Term,
rest: HashMap<S::IRI, Vec<IncomingNeighsNode<S>>>,
},
}