use crate::{
CompactFragment,
CompactIndexedFragment,
Error,
Options,
add_value,
compact_collection_with,
compact_iri,
compact_iri_with,
compact_iri_with_memo,
iri::{CompactIriMemo, keyword_alias},
value_value,
};
use jsonld_core::{
Container,
ContainerKind,
Context,
Id,
Indexed,
Loader,
Node,
Object,
Term,
context::Nest,
object::{self, List},
};
use jsonld_syntax::{Keyword, native_stack::Recursion};
use rdfx::vocabulary::VocabularyMut;
use std::hash::Hash;
async fn compact_property_list<N, L>(
vocabulary: &mut N,
list: &List<N::Iri, N::BlankId>,
expanded_index: Option<&str>,
nest_result: &mut jstrict::Object,
container: Container,
as_array: bool,
item_active_property: &str,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
options: Options,
) -> Result<(), Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
L: Loader,
{
let mut compacted_item = Recursion::new(compact_collection_with(
vocabulary,
list.iter(),
active_context,
active_context,
Some(item_active_property),
loader,
options,
))
.await?;
if !compacted_item.is_array() {
let array = vec![compacted_item];
compacted_item = jstrict::Value::Array(array);
}
if container.contains(ContainerKind::List) {
nest_result.insert(item_active_property.into(), compacted_item);
} else {
let key = keyword_alias(vocabulary, active_context, options, Keyword::List);
let mut compacted_item_list_object = jstrict::Object::default();
compacted_item_list_object.insert(key.into(), compacted_item);
if let Some(index) = expanded_index {
let key = keyword_alias(vocabulary, active_context, options, Keyword::Index);
compacted_item_list_object.insert(key.into(), jstrict::Value::String(index.into()));
}
compacted_item = jstrict::Value::Object(compacted_item_list_object);
add_value(nest_result, item_active_property, compacted_item, as_array);
}
Ok(())
}
async fn compact_property_graph<N, L>(
vocabulary: &mut N,
node: &Node<N::Iri, N::BlankId>,
expanded_index: Option<&str>,
nest_result: &mut jstrict::Object,
container: Container,
as_array: bool,
item_active_property: &str,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
options: Options,
) -> Result<(), Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
L: Loader,
{
let Some(graph) = node.graph() else { return Ok(()) };
let mut compacted_item =
Recursion::new(graph.compact_fragment_full(vocabulary, active_context, active_context, Some(item_active_property), loader, options)).await?;
if container.contains(ContainerKind::Graph) && container.contains(ContainerKind::Id) {
if nest_result.get_unique(item_active_property).is_ok_and(|o| o.is_none()) {
nest_result.insert(item_active_property.into(), jsonld_syntax::Object::default().into());
}
let Some(map_object) = nest_result
.get_unique_mut(item_active_property)
.ok()
.flatten()
.and_then(jstrict::Value::as_object_mut)
else {
return Err(Error::CollidingEntry);
};
let (id_value, vocab) = match &node.id {
Some(entry) => (entry.clone().into_term(), false),
None => (Term::Keyword(Keyword::None), true),
};
let Some(map_key) = compact_iri(vocabulary, active_context, &id_value, vocab, false, options)? else {
return Err(Error::IriConfusedWithPrefix);
};
add_value(map_object, &map_key, compacted_item, as_array);
} else if container.contains(ContainerKind::Graph) && container.contains(ContainerKind::Index) && node.is_simple_graph() {
if nest_result.get_unique(item_active_property).is_ok_and(|o| o.is_none()) {
nest_result.insert(item_active_property.into(), jsonld_syntax::Object::default().into());
}
let Some(map_object) = nest_result
.get_unique_mut(item_active_property)
.ok()
.flatten()
.and_then(jstrict::Value::as_object_mut)
else {
return Err(Error::CollidingEntry);
};
let map_key = expanded_index.unwrap_or("@none");
add_value(map_object, map_key, compacted_item, as_array);
} else if container.contains(ContainerKind::Graph) && node.is_simple_graph() {
compacted_item = match compacted_item {
jstrict::Value::Array(items) if items.len() > 1 => {
let key = keyword_alias(vocabulary, active_context, options, Keyword::Included);
let mut map = jstrict::Object::default();
map.insert(key.into(), jstrict::Value::Array(items));
jstrict::Value::Object(map)
}
item => item,
};
add_value(nest_result, item_active_property, compacted_item, as_array);
} else {
let key = keyword_alias(vocabulary, active_context, options, Keyword::Graph);
let mut map = jstrict::Object::default();
map.insert(key.into(), compacted_item);
if let Some(id_entry) = &node.id {
let id: Term<N::Iri, N::BlankId> = id_entry.clone().into();
let value = compact_iri(vocabulary, active_context, &id, false, false, options)?;
map.insert(
Keyword::Id.into_str().into(),
match value {
Some(s) => jstrict::Value::String((&*s).into()),
None => jstrict::Value::Null,
},
);
}
if let Some(index_entry) = expanded_index {
let key = keyword_alias(vocabulary, active_context, options, Keyword::Index);
map.insert(key.into(), index_entry.into());
}
let compacted_item = jstrict::Value::Object(map);
add_value(nest_result, item_active_property, compacted_item, as_array);
}
Ok(())
}
fn select_nest_result<'a, I, B, E>(
result: &'a mut jstrict::Object,
active_context: &Context<I, B>,
item_active_property: &str,
compact_arrays: bool,
) -> Result<(&'a mut jstrict::Object, Container, bool), Error<E>>
where
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
{
let (nest_result, container) = match active_context.get(item_active_property) {
Some(term_definition) => {
let nest_result = match term_definition.nest() {
Some(nest_term) => {
if *nest_term != Nest::Nest {
match active_context.get(nest_term.as_str()) {
Some(term_def) if term_def.value() == Some(&Term::Keyword(Keyword::Nest)) => {}
_ => return Err(Error::InvalidNestValue),
}
}
if result.get_unique(nest_term.as_str()).is_ok_and(|o| o.is_none()) {
result.insert(nest_term.as_str().into(), jstrict::Object::default().into());
}
match result.get_unique_mut(nest_term.as_str()).ok().flatten().and_then(jstrict::Value::as_object_mut) {
Some(object) => object,
None => return Err(Error::CollidingEntry),
}
}
None => {
result
}
};
(nest_result, term_definition.container())
}
None => (result, Container::None),
};
let as_array = values_as_array(container, item_active_property, compact_arrays);
Ok((nest_result, container, as_array))
}
#[must_use]
pub fn values_as_array(container: Container, item_active_property: &str, compact_arrays: bool) -> bool {
if container.contains(ContainerKind::Set) || item_active_property == "@graph" || item_active_property == "@list" {
true
} else {
!compact_arrays
}
}
pub async fn compact_property<'a, N, L, O, T>(
vocabulary: &mut N,
result: &mut jstrict::Object,
expanded_property: Term<N::Iri, N::BlankId>,
expanded_value: O,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
inside_reverse: bool,
options: Options,
) -> Result<(), Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
O: IntoIterator<Item = &'a Indexed<T>>,
T: 'a + object::Any<N::Iri, N::BlankId>,
L: Loader,
{
let mut is_empty = true;
let mut memo = CompactIriMemo::new();
for expanded_item in expanded_value {
is_empty = false;
let item_active_property = compact_iri_with_memo(
vocabulary,
active_context,
&expanded_property,
expanded_item,
true,
inside_reverse,
options,
&mut memo,
)?;
if let Some(item_active_property) = item_active_property {
let (nest_result, container, as_array) = select_nest_result(result, active_context, &item_active_property, options.compact_arrays)?;
match expanded_item.inner().as_ref() {
object::Ref::List(list) => {
compact_property_list(
vocabulary,
list,
expanded_item.index(),
nest_result,
container,
as_array,
&item_active_property,
active_context,
loader,
options,
)
.await?;
}
object::Ref::Node(node) if node.is_graph() => {
compact_property_graph(
vocabulary,
node,
expanded_item.index(),
nest_result,
container,
as_array,
&item_active_property,
active_context,
loader,
options,
)
.await?;
}
_ => {
let mut compacted_item = Recursion::new(expanded_item.compact_fragment_full(
vocabulary,
active_context,
active_context,
Some(&item_active_property),
loader,
options,
))
.await?;
if !container.contains(ContainerKind::Graph)
&& (container.contains(ContainerKind::Language)
|| container.contains(ContainerKind::Index)
|| container.contains(ContainerKind::Id)
|| container.contains(ContainerKind::Type))
{
if nest_result.get_unique(&*item_active_property).is_ok_and(|o| o.is_none()) {
nest_result.insert((&*item_active_property).into(), jstrict::Object::default().into());
}
let Some(map_object) = nest_result
.get_unique_mut(&*item_active_property)
.ok()
.flatten()
.and_then(jstrict::Value::as_object_mut)
else {
return Err(Error::CollidingEntry);
};
let container_type = if container.contains(ContainerKind::Language) {
ContainerKind::Language
} else if container.contains(ContainerKind::Index) {
ContainerKind::Index
} else if container.contains(ContainerKind::Id) {
ContainerKind::Id
} else {
ContainerKind::Type
};
let mut container_key: String = keyword_alias(vocabulary, active_context, options, container_type.into()).to_string();
let index_key = match active_context.get(&*item_active_property) {
Some(def) if def.index().is_some() => def.index(),
_ => None,
};
let map_key = if container_type == ContainerKind::Language && expanded_item.is_value() {
if let object::Ref::Value(value) = expanded_item.inner().as_ref() {
compacted_item = value_value(value);
}
expanded_item.language().map(std::string::ToString::to_string)
} else if container_type == ContainerKind::Index {
match index_key {
Some(index_key) => {
container_key =
match compact_iri(vocabulary, active_context, &Term::Id(Id::Invalid(index_key.to_string())), true, false, options)? {
Some(arc) => arc.to_string(),
None => return Err(Error::IriConfusedWithPrefix),
};
let (map_key, remaining_values) = match &mut compacted_item {
jstrict::Value::Object(map) => match map.remove_unique(container_key.as_str()).ok().flatten() {
Some(entry) => match entry.value {
jstrict::Value::String(s) => (Some(s.to_string()), Vec::new()),
jstrict::Value::Array(values) => {
let mut values = values.into_iter();
match values.next() {
Some(first_value) => (first_value.as_str().map(std::string::ToString::to_string), values.collect()),
None => (None, values.collect()),
}
}
other => (None, vec![other]),
},
None => (None, Vec::new()),
},
_ => (None, Vec::new()),
};
if !remaining_values.is_empty()
&& let Some(map) = compacted_item.as_object_mut()
{
for value in remaining_values {
add_value(map, container_key.as_str(), value, false);
}
}
map_key
}
None => {
expanded_item.index().map(ToOwned::to_owned)
}
}
} else if container_type == ContainerKind::Id {
compacted_item
.as_object_mut()
.and_then(|map| {
map.remove_unique(container_key.as_str())
.ok()
.flatten()
.map(|entry| entry.value.as_str().map(ToOwned::to_owned))
})
.flatten()
} else {
let (map_key, remaining_values) = match compacted_item.as_object_mut() {
Some(map) => match map.remove_unique(container_key.as_str()).ok().flatten() {
Some(entry) => match entry.value {
jstrict::Value::String(s) => (Some((*s).to_string()), Vec::new()),
jstrict::Value::Array(values) => {
let mut values = values.into_iter();
match values.next() {
Some(first_value) => (first_value.as_str().map(ToOwned::to_owned), values.collect()),
None => (None, values.collect()),
}
}
other => (None, vec![other]),
},
None => (None, Vec::new()),
},
_ => (None, Vec::new()),
};
if !remaining_values.is_empty()
&& let Some(map) = compacted_item.as_object_mut()
{
for value in remaining_values {
add_value(map, container_key.as_str(), value, false);
}
}
if let Some(map) = compacted_item.as_object()
&& map.len() == 1
&& map.get_unique("@id").ok().flatten().is_some()
&& let Some(id) = expanded_item.id()
{
let obj = Object::node(Node::with_id(id.clone()));
compacted_item = Recursion::new(obj.compact_indexed_fragment(
vocabulary,
None,
active_context,
active_context,
Some(&item_active_property),
loader,
options,
))
.await?;
}
map_key
};
let map_key = match map_key {
Some(key) => key,
None => keyword_alias(vocabulary, active_context, options, Keyword::None).to_string(),
};
add_value(map_object, &map_key, compacted_item, as_array);
} else {
add_value(nest_result, &item_active_property, compacted_item, as_array);
}
}
}
}
}
if is_empty {
let item_active_property = compact_iri_with(
vocabulary,
active_context,
&expanded_property,
&Indexed::new(Object::node(Node::new()), None),
true,
inside_reverse,
options,
)?;
if let Some(item_active_property) = item_active_property {
let (nest_result, _, _) = select_nest_result(result, active_context, &item_active_property, options.compact_arrays)?;
add_value(nest_result, &item_active_property, Vec::new().into(), true);
}
}
Ok(())
}