use super::{FlatteningError, FlatteningOptions, JsonLdValue};
use indexmap::IndexMap;
#[derive(Debug, Default)]
pub struct BlankNodeIdMapper {
mapping: IndexMap<String, String>,
counter: u64,
}
impl BlankNodeIdMapper {
pub fn new() -> Self {
Self::default()
}
pub fn map(&mut self, original: &str) -> String {
if let Some(mapped) = self.mapping.get(original) {
return mapped.clone();
}
let new_id = format!("_:b{}", self.counter);
self.counter += 1;
self.mapping.insert(original.to_string(), new_id.clone());
new_id
}
pub fn reset(&mut self) {
self.mapping.clear();
self.counter = 0;
}
}
#[derive(Debug, Clone, Default)]
pub struct NodeObject {
pub id: String,
pub types: Vec<String>,
pub properties: IndexMap<String, Vec<JsonLdValue>>,
}
impl NodeObject {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
..Default::default()
}
}
pub fn merge_property(&mut self, property: &str, value: JsonLdValue) {
let values = self.properties.entry(property.to_string()).or_default();
merge_value(values, value);
}
}
#[derive(Debug, Clone, Default)]
pub struct GraphNodeMap {
pub nodes: IndexMap<String, NodeObject>,
}
impl GraphNodeMap {
pub fn new() -> Self {
Self::default()
}
pub fn get_or_create(&mut self, id: &str) -> &mut NodeObject {
self.nodes
.entry(id.to_string())
.or_insert_with(|| NodeObject::new(id))
}
}
#[derive(Debug, Clone, Default)]
pub struct NodeMap {
pub graphs: IndexMap<String, GraphNodeMap>,
}
impl NodeMap {
pub fn new() -> Self {
let mut nm = Self::default();
nm.graphs
.insert("@default".to_string(), GraphNodeMap::new());
nm
}
pub fn get_or_create_graph(&mut self, graph_name: &str) -> &mut GraphNodeMap {
self.graphs.entry(graph_name.to_string()).or_default()
}
pub fn default_graph(&self) -> &GraphNodeMap {
self.graphs
.get("@default")
.expect("@default graph always present")
}
pub fn default_graph_mut(&mut self) -> &mut GraphNodeMap {
self.graphs
.get_mut("@default")
.expect("@default graph always present")
}
}
pub fn generate_node_map(
expanded: &[JsonLdValue],
options: &FlatteningOptions,
) -> Result<NodeMap, FlatteningError> {
let mut node_map = NodeMap::new();
let mut blank_mapper = BlankNodeIdMapper::new();
for element in expanded {
generate_node_map_element(
element,
&mut node_map,
"@default",
None,
None,
&mut blank_mapper,
options,
)?;
}
Ok(node_map)
}
#[allow(clippy::too_many_arguments)]
pub fn generate_node_map_element(
element: &JsonLdValue,
node_map: &mut NodeMap,
active_graph: &str,
active_subject: Option<&str>,
active_property: Option<&str>,
blank_mapper: &mut BlankNodeIdMapper,
_options: &FlatteningOptions,
) -> Result<(), FlatteningError> {
match element {
JsonLdValue::Array(items) => {
for item in items {
generate_node_map_element(
item,
node_map,
active_graph,
active_subject,
active_property,
blank_mapper,
_options,
)?;
}
return Ok(());
}
JsonLdValue::Null | JsonLdValue::Bool(_) | JsonLdValue::Number(_) | JsonLdValue::Str(_) => {
if let (Some(subj), Some(prop)) = (active_subject, active_property) {
let graph = node_map.get_or_create_graph(active_graph);
graph
.get_or_create(subj)
.merge_property(prop, element.clone());
}
return Ok(());
}
JsonLdValue::Object(_) => {} }
let obj = match element {
JsonLdValue::Object(m) => m,
_ => unreachable!("handled above"),
};
if obj.contains_key("@value") {
if let (Some(subj), Some(prop)) = (active_subject, active_property) {
let graph = node_map.get_or_create_graph(active_graph);
graph
.get_or_create(subj)
.merge_property(prop, element.clone());
}
return Ok(());
}
if obj.contains_key("@list") {
let list_items = match obj.get("@list") {
Some(JsonLdValue::Array(items)) => items.clone(),
_ => Vec::new(),
};
let mut processed: Vec<JsonLdValue> = Vec::new();
for item in &list_items {
let item_copy = resolve_node_reference(item, node_map, active_graph, blank_mapper);
processed.push(item_copy);
}
let list_obj = {
let mut m: IndexMap<String, JsonLdValue> = IndexMap::new();
m.insert("@list".to_string(), JsonLdValue::Array(processed));
JsonLdValue::Object(m)
};
if let (Some(subj), Some(prop)) = (active_subject, active_property) {
let graph = node_map.get_or_create_graph(active_graph);
graph.get_or_create(subj).merge_property(prop, list_obj);
}
return Ok(());
}
let id = get_or_assign_id(obj, blank_mapper);
node_map
.get_or_create_graph(active_graph)
.get_or_create(&id);
if let (Some(subj), Some(prop)) = (active_subject, active_property) {
let ref_obj = {
let mut m: IndexMap<String, JsonLdValue> = IndexMap::new();
m.insert("@id".to_string(), JsonLdValue::Str(id.clone()));
JsonLdValue::Object(m)
};
let graph = node_map.get_or_create_graph(active_graph);
graph.get_or_create(subj).merge_property(prop, ref_obj);
}
if let Some(graph_val) = obj.get("@graph") {
{
let def_graph = node_map.default_graph_mut();
def_graph.get_or_create(&id);
}
let graph_items = match graph_val {
JsonLdValue::Array(items) => items.clone(),
single => vec![single.clone()],
};
for item in &graph_items {
generate_node_map_element(item, node_map, &id, None, None, blank_mapper, _options)?;
}
}
if let Some(type_val) = obj.get("@type") {
let type_iris: Vec<String> = match type_val {
JsonLdValue::Array(items) => items
.iter()
.filter_map(|v| v.as_str())
.map(|s| {
if s.starts_with("_:") {
blank_mapper.map(s)
} else {
s.to_string()
}
})
.collect(),
JsonLdValue::Str(s) => {
let mapped = if s.starts_with("_:") {
blank_mapper.map(s)
} else {
s.to_string()
};
vec![mapped]
}
_ => Vec::new(),
};
let graph = node_map.get_or_create_graph(active_graph);
let node = graph.get_or_create(&id);
for t in type_iris {
if !node.types.contains(&t) {
node.types.push(t.clone());
}
merge_value(
node.properties.entry("@type".to_string()).or_default(),
JsonLdValue::Str(t),
);
}
}
if let Some(JsonLdValue::Object(rev_obj)) = obj.get("@reverse") {
for (rev_prop, rev_val) in rev_obj {
let rev_items: Vec<JsonLdValue> = match rev_val {
JsonLdValue::Array(a) => a.clone(),
single => vec![single.clone()],
};
for rev_item in &rev_items {
let rev_item_id = match rev_item {
JsonLdValue::Object(m) => get_or_assign_id(m, blank_mapper),
_ => continue,
};
node_map
.get_or_create_graph(active_graph)
.get_or_create(&rev_item_id);
let ref_obj = {
let mut m: IndexMap<String, JsonLdValue> = IndexMap::new();
m.insert("@id".to_string(), JsonLdValue::Str(id.clone()));
JsonLdValue::Object(m)
};
let graph = node_map.get_or_create_graph(active_graph);
graph
.get_or_create(&rev_item_id)
.merge_property(rev_prop, ref_obj);
}
}
}
for (prop, value) in obj {
match prop.as_str() {
"@id" | "@type" | "@graph" | "@reverse" | "@context" => continue,
_ => {}
}
let values: Vec<JsonLdValue> = match value {
JsonLdValue::Array(a) => a.clone(),
single => vec![single.clone()],
};
for v in values {
generate_node_map_element(
&v,
node_map,
active_graph,
Some(&id),
Some(prop),
blank_mapper,
_options,
)?;
}
}
Ok(())
}
pub fn node_map_to_flat_array(node_map: &NodeMap, ordered: bool) -> Vec<JsonLdValue> {
let default_graph = node_map.default_graph();
let mut subjects: Vec<&str> = default_graph.nodes.keys().map(String::as_str).collect();
if ordered {
subjects.sort_unstable();
}
let mut result: Vec<JsonLdValue> = Vec::with_capacity(subjects.len());
for subj_id in subjects {
let node_obj = &default_graph.nodes[subj_id];
let mut map: IndexMap<String, JsonLdValue> = IndexMap::new();
map.insert("@id".to_string(), JsonLdValue::Str(subj_id.to_string()));
if !node_obj.types.is_empty() {
let types: Vec<JsonLdValue> = node_obj
.types
.iter()
.map(|t| JsonLdValue::Str(t.clone()))
.collect();
map.insert("@type".to_string(), JsonLdValue::Array(types));
}
let mut props: Vec<&str> = node_obj.properties.keys().map(String::as_str).collect();
if ordered {
props.sort_unstable();
}
for prop in props {
if prop == "@type" {
continue;
}
let values = &node_obj.properties[prop];
if !values.is_empty() {
map.insert(prop.to_string(), JsonLdValue::Array(values.clone()));
}
}
if node_map.graphs.contains_key(subj_id) && subj_id != "@default" {
let inner_graph = &node_map.graphs[subj_id];
let mut inner_subjects: Vec<&str> =
inner_graph.nodes.keys().map(String::as_str).collect();
if ordered {
inner_subjects.sort_unstable();
}
let inner_array: Vec<JsonLdValue> = inner_subjects
.iter()
.map(|s| {
let inner_node = &inner_graph.nodes[*s];
serialise_node_object(inner_node, ordered)
})
.collect();
map.insert("@graph".to_string(), JsonLdValue::Array(inner_array));
}
result.push(JsonLdValue::Object(map));
}
result
}
fn serialise_node_object(node: &NodeObject, ordered: bool) -> JsonLdValue {
let mut map: IndexMap<String, JsonLdValue> = IndexMap::new();
map.insert("@id".to_string(), JsonLdValue::Str(node.id.clone()));
if !node.types.is_empty() {
let types: Vec<JsonLdValue> = node
.types
.iter()
.map(|t| JsonLdValue::Str(t.clone()))
.collect();
map.insert("@type".to_string(), JsonLdValue::Array(types));
}
let mut props: Vec<&str> = node.properties.keys().map(String::as_str).collect();
if ordered {
props.sort_unstable();
}
for prop in props {
if prop == "@type" {
continue;
}
let values = &node.properties[prop];
if !values.is_empty() {
map.insert(prop.to_string(), JsonLdValue::Array(values.clone()));
}
}
JsonLdValue::Object(map)
}
fn get_or_assign_id(
obj: &IndexMap<String, JsonLdValue>,
blank_mapper: &mut BlankNodeIdMapper,
) -> String {
match obj.get("@id") {
Some(JsonLdValue::Str(id)) => {
if id.starts_with("_:") {
blank_mapper.map(id)
} else {
id.clone()
}
}
_ => {
let anon = format!("_:anon_{}", blank_mapper.counter);
blank_mapper.map(&anon)
}
}
}
fn resolve_node_reference(
value: &JsonLdValue,
node_map: &mut NodeMap,
active_graph: &str,
blank_mapper: &mut BlankNodeIdMapper,
) -> JsonLdValue {
match value {
JsonLdValue::Object(m) if !m.contains_key("@value") && !m.contains_key("@list") => {
let id = get_or_assign_id(m, blank_mapper);
node_map
.get_or_create_graph(active_graph)
.get_or_create(&id);
let mut ref_map: IndexMap<String, JsonLdValue> = IndexMap::new();
ref_map.insert("@id".to_string(), JsonLdValue::Str(id));
JsonLdValue::Object(ref_map)
}
other => other.clone(),
}
}
pub fn merge_value(into: &mut Vec<JsonLdValue>, value: JsonLdValue) {
if into.contains(&value) {
return;
}
into.push(value);
}