use btree_indexmap::BTreeIndexMultiSet;
use json_syntax::{JsonObject, JsonValue};
use mown::Mown;
use rdf_syntax::Id;
use crate::{
JsonLdBacktraceBuilder, JsonLdLenient, JsonLdLocatedError, JsonLdNode, JsonLdProcessingMode,
JsonLdTerm, JsonLdType,
algorithms::{
AsyncJsonLdEnvironment, AsyncProcessingEnvironmentRef,
compaction::{Compactor, object::value::add_value},
context_processing::JsonLdContextProcessingOptions,
},
context::JsonLdContainer,
syntax::{JsonLdContainerItem, JsonLdKeyword},
};
mod property;
fn optional_string(s: Option<String>) -> JsonValue {
s.map(Into::into).unwrap_or_else(|| JsonValue::Null)
}
impl Compactor<'_> {
#[allow(clippy::too_many_arguments)]
pub async fn compact_indexed_node_with(
&self,
env: &impl AsyncJsonLdEnvironment,
node: &JsonLdNode,
index: Option<&str>,
location: JsonLdBacktraceBuilder<'_>,
) -> Result<JsonValue, JsonLdLocatedError> {
let mut active_context = Mown::Borrowed(self.active_context);
if !(node.is_empty() && node.id.is_some()) {
if let Some(previous_context) = self.active_context.previous_context() {
active_context = Mown::Borrowed(previous_context);
}
}
if let Some(active_property) = self.active_property
&& let Some(active_property_definition) = self.type_scoped_context.get(active_property)
&& let Some(local_context) = active_property_definition.context()
{
let def_loc =
location.push_backtrace(active_property_definition.location().unwrap_or_default());
let scoped_context_loc = def_loc.object_value(JsonLdKeyword::Context);
active_context = Mown::Owned(
local_context
.process_with(
AsyncProcessingEnvironmentRef(env),
active_property_definition.base_url(),
active_context.as_ref(),
JsonLdContextProcessingOptions::from(self.options).with_override(),
scoped_context_loc,
)
.await?
.into_raw(),
)
}
let mut result = JsonObject::default();
if let Some(types) = &node.types
&& !types.is_empty()
{
let type_loc = location.object_entry("@type");
let mut compacted_types = Vec::new();
for (i, ty) in types.iter().enumerate() {
let compacted_ty = self
.with_active_context(self.type_scoped_context)
.compact_iri(
&ty.clone().into_term(),
true,
false,
type_loc.array_index(i),
)?;
compacted_types.push(compacted_ty)
}
compacted_types.sort_by(|a, b| a.as_ref().unwrap().cmp(b.as_ref().unwrap()));
for term in &compacted_types {
if let Some(term_definition) = self
.type_scoped_context
.get(term.as_ref().unwrap().as_str())
&& let Some(local_context) = term_definition.context()
{
let processing_options =
JsonLdContextProcessingOptions::from(self.options).without_propagation();
let def_loc =
type_loc.push_backtrace(term_definition.location().unwrap_or_default());
let scoped_context_loc = def_loc.object_value(JsonLdKeyword::Context);
active_context = Mown::Owned(
local_context
.process_with(
AsyncProcessingEnvironmentRef(env),
term_definition.base_url(),
active_context.as_ref(),
processing_options,
scoped_context_loc,
)
.await?
.into_raw(),
)
}
}
}
let mut expanded_entries: Vec<_> = node.properties().iter().collect();
if self.options.ordered {
expanded_entries.sort_by(|(a, _), (b, _)| a.as_str().cmp(b.as_str()))
}
if let Some(id_entry) = &node.id {
let id = id_entry.clone().into_term();
let id_loc = location.object_entry("@id");
if node.is_empty() {
let type_mapping = match self.active_property {
Some(prop) => match active_context.get(prop) {
Some(def) => def.typ(),
None => None,
},
None => None,
};
if type_mapping == Some(&JsonLdType::Id) {
let compacted_value = self
.with_active_context(&active_context)
.compact_iri(&id, false, false, id_loc)?;
return Ok(optional_string(compacted_value));
}
if type_mapping == Some(&JsonLdType::Vocab) {
let compacted_value = self
.with_active_context(&active_context)
.compact_iri(&id, true, false, id_loc)?;
return Ok(optional_string(compacted_value));
}
}
let compacted_value = self
.with_active_context(&active_context)
.compact_iri(&id, false, false, id_loc)?;
let alias = self.with_active_context(&active_context).compact_iri(
&JsonLdTerm::Keyword(JsonLdKeyword::Id),
true,
false,
id_loc,
)?;
if let Some(key) = alias {
result.insert(key, optional_string(compacted_value));
}
}
self.with_active_context(&active_context).compact_types(
&mut result,
node.types.as_ref(),
location,
)?;
if let Some(reverse_properties) = node.reverse_properties_entry()
&& !reverse_properties.is_empty()
{
let rev_loc = location.object_entry("@reverse");
let active_property = "@reverse";
if let Some(active_property_definition) = active_context.get(active_property)
&& let Some(local_context) = active_property_definition.context()
{
let def_loc = rev_loc
.push_backtrace(active_property_definition.location().unwrap_or_default());
let scoped_context_loc = def_loc.object_value(JsonLdKeyword::Context);
active_context = Mown::Owned(
local_context
.process_with(
AsyncProcessingEnvironmentRef(env),
active_property_definition.base_url(),
active_context.as_ref(),
JsonLdContextProcessingOptions::from(self.options).with_override(),
scoped_context_loc,
)
.await?
.into_raw(),
)
}
let mut reverse_result = JsonObject::default();
for (expanded_property, expanded_value) in reverse_properties.iter() {
let prop_loc = rev_loc.object_entry(expanded_property.as_str());
self.with_active_context(&active_context)
.compact_property(
env,
&mut reverse_result,
expanded_property.clone().into(),
expanded_value.iter(),
true,
prop_loc,
)
.await?;
}
let mut reverse_map = JsonObject::default();
for (property, mapped_value) in reverse_result.iter_mut() {
let mut value = JsonValue::Null;
std::mem::swap(&mut value, &mut *mapped_value);
if let Some(term_definition) = active_context.get(property.as_str())
&& term_definition.reverse_property()
{
let as_array = term_definition
.container()
.contains(JsonLdContainerItem::Set)
|| !self.options.compact_arrays;
add_value(&mut result, property, value, as_array);
continue;
}
reverse_map.insert(property.clone(), value);
}
if !reverse_map.is_empty() {
let alias = self.with_active_context(&active_context).compact_iri(
&JsonLdTerm::Keyword(JsonLdKeyword::Reverse),
true,
false,
rev_loc,
)?;
result.insert(alias.unwrap(), reverse_map.into());
}
}
if let Some(index_entry) = index {
let index_loc = location.object_entry("@index");
let mut index_container = false;
if let Some(active_property) = self.active_property
&& let Some(active_property_definition) = active_context.get(active_property)
&& active_property_definition
.container()
.contains(JsonLdContainerItem::Index)
{
index_container = true;
}
if !index_container {
let alias = self.with_active_context(&active_context).compact_iri(
&JsonLdTerm::Keyword(JsonLdKeyword::Index),
true,
false,
index_loc,
)?;
result.insert(alias.unwrap(), index_entry.into());
}
}
if let Some(graph_entry) = node.graph_entry() {
let graph_loc = location.object_entry("@graph");
self.with_active_context(&active_context)
.compact_property(
env,
&mut result,
JsonLdTerm::Keyword(JsonLdKeyword::Graph),
graph_entry.iter(),
false,
graph_loc,
)
.await?
}
for (expanded_property, expanded_value) in expanded_entries {
let prop_loc = location.object_entry(expanded_property.as_str());
self.with_active_context(&active_context)
.compact_property(
env,
&mut result,
expanded_property.clone().into(),
expanded_value.iter(),
false,
prop_loc,
)
.await?
}
if let Some(included_entry) = node.included_entry() {
let included_loc = location.object_entry("@included");
self.with_active_context(&active_context)
.compact_property(
env,
&mut result,
JsonLdTerm::Keyword(JsonLdKeyword::Included),
included_entry.iter(),
false,
included_loc,
)
.await?
}
Ok(result.into())
}
fn compact_types(
self,
result: &mut JsonObject,
types: Option<&BTreeIndexMultiSet<JsonLdLenient<Id>>>,
location: JsonLdBacktraceBuilder<'_>,
) -> Result<(), JsonLdLocatedError> {
if let Some(types) = types
&& !types.is_empty()
{
let type_loc = location.object_entry("@type");
let compacted_value = if types.len() == 1 {
optional_string(
self.with_active_context(self.type_scoped_context)
.compact_iri(
&types[0].clone().into_term(),
true,
false,
type_loc.array_index(0),
)?,
)
} else {
let mut compacted_value = Vec::with_capacity(types.len());
for (i, ty) in types.iter().enumerate() {
let ty = ty.clone().into_term();
let compacted_ty = self
.with_active_context(self.type_scoped_context)
.compact_iri(&ty, true, false, type_loc.array_index(i))?;
compacted_value.push(optional_string(compacted_ty))
}
JsonValue::Array(compacted_value.into_iter().collect())
};
let alias = self
.compact_iri(
&JsonLdTerm::Keyword(JsonLdKeyword::Type),
true,
false,
type_loc,
)?
.unwrap();
let container_mapping = match self.active_context.get(alias.as_str()) {
Some(def) => def.container(),
None => JsonLdContainer::Null,
};
let as_array = (self.options.processing_mode == JsonLdProcessingMode::JsonLd1_1
&& container_mapping.contains(JsonLdContainerItem::Set))
|| !self.options.compact_arrays;
add_value(result, &alias, compacted_value, as_array)
}
Ok(())
}
}