use std::borrow::Cow;
use into_owned_trait::IntoOwned;
use json_syntax::{JsonValue, object::Entry};
use mown::Mown;
use rdf_syntax::Id;
use crate::{
JsonLdBacktraceBuilder, JsonLdError, JsonLdIndexed, JsonLdLenient, JsonLdLocatedError,
JsonLdNullable, JsonLdObject, JsonLdTerm, JsonLdWarning,
algorithms::{
AsyncJsonLdEnvironment, AsyncProcessingEnvironmentRef,
context_processing::JsonLdContextProcessingOptions,
},
object::JsonLdList,
syntax::{JsonLdContext, JsonLdKeyword},
};
use super::{ExpandableLiteralValue, Expanded, Expander};
pub struct ExpandedEntry<'a> {
pub key: &'a str,
pub expanded_key: JsonLdTerm,
pub value: &'a JsonValue,
}
impl<'a> Expander<'a> {
#[allow(clippy::too_many_arguments)]
pub async fn expand_element(
&self,
env: &impl AsyncJsonLdEnvironment,
element: &JsonValue,
from_map: bool,
location: JsonLdBacktraceBuilder<'_>,
) -> Result<Expanded, JsonLdLocatedError> {
if element.is_null() {
return Ok(Expanded::Null);
}
let active_property_definition = self.active_property_definition();
let mut property_scoped_base_url = None;
let property_scoped_context = if let Some(definition) = active_property_definition {
if let Some(base_url) = definition.base_url() {
property_scoped_base_url = Some(base_url);
}
definition.context()
} else {
None
};
match element {
JsonValue::Null => unreachable!(),
JsonValue::Array(element) => {
self.expand_array(env, active_property_definition, element, from_map, location)
.await
}
JsonValue::Object(element) => {
let mut preliminary_value_entry = None;
let mut preliminary_id_entry = None;
for (key, value) in element.entries() {
match self.active_context.expand_iri(
JsonLdNullable::Some(key.as_str().into()),
false,
true,
) {
JsonLdTerm::Keyword(JsonLdKeyword::Value) => {
preliminary_value_entry = Some(value.clone())
}
JsonLdTerm::Keyword(JsonLdKeyword::Id) => {
preliminary_id_entry = Some(value.clone())
}
_ => (),
}
}
let mut active_context = Mown::Borrowed(self.active_context);
if let Some(previous_context) = active_context.previous_context() {
if !from_map
&& preliminary_value_entry.is_none()
&& !(element.len() == 1 && preliminary_id_entry.is_some())
{
active_context = Mown::Owned(previous_context.clone())
}
}
if let Some(property_scoped_context) = property_scoped_context {
let options: JsonLdContextProcessingOptions = self.options.into();
active_context = Mown::Owned(
property_scoped_context
.process_with(
env.as_ref(),
property_scoped_base_url,
active_context.as_ref(),
options.with_override(),
location,
)
.await?
.into_raw(),
);
}
if let Some(local_context) = element
.get_unique("@context")
.map_err(|e| JsonLdError::duplicate_key_ref(e).at(location))?
{
let local_context: JsonLdContext =
json_syntax::from_value(local_context.clone()).map_err(|e| {
JsonLdError::ContextSyntax(e.to_string())
.at(location.object_value(JsonLdKeyword::Context))
})?;
let context_loc = location.object_value(JsonLdKeyword::Context);
active_context = Mown::Owned(
local_context
.process_with(
env.as_ref(),
self.base_url,
&active_context,
self.options.into(),
context_loc,
)
.await?
.into_raw(),
);
}
let entries: Cow<[Entry]> = if self.options.ordered {
Cow::Owned(element.iter_sorted().map(IntoOwned::into_owned).collect())
} else {
Cow::Borrowed(element.entries())
};
let mut type_entries: Vec<&Entry> = Vec::new();
for entry @ (key, _) in entries.iter() {
let expanded_key = active_context.expand_iri(
JsonLdNullable::Some(key.as_str().into()),
false,
true,
);
if let JsonLdTerm::Keyword(JsonLdKeyword::Type) = expanded_key {
type_entries.push(entry);
}
}
type_entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
let type_scoped_context = active_context.as_ref();
let mut active_context = Mown::Borrowed(active_context.as_ref());
for (_, value) in &type_entries {
let value = value.force_as_array();
let mut sorted_value: Vec<&str> =
value.iter().filter_map(JsonValue::as_str).collect();
sorted_value.sort_unstable();
for term in sorted_value {
if let Some(term_definition) = type_scoped_context.get(term)
&& let Some(local_context) = term_definition.context()
{
let options: JsonLdContextProcessingOptions = self.options.into();
active_context = Mown::Owned(
local_context
.process_with(
env.as_ref(),
term_definition.base_url(),
active_context.as_ref(),
options.without_propagation(),
location,
)
.await?
.into_raw(),
);
}
}
}
let input_type = if let Some((_, value)) = type_entries.first() {
let value = value.force_as_array();
if let Some(input_type) = value.last() {
input_type.as_string().map(|input_type_str| {
active_context.expand_iri(
JsonLdNullable::Some(input_type_str.into()),
false,
true,
)
})
} else {
None
}
} else {
None
};
let mut expanded_entries: Vec<ExpandedEntry> = Vec::with_capacity(element.len());
let mut list_entry = None;
let mut set_entry = None;
let mut value_entry = None;
for (key, value) in entries.iter() {
if key.is_empty() {
env.warn(JsonLdWarning::EmptyTerm);
}
let expanded_key = active_context.expand_iri(
JsonLdNullable::Some(key.as_str().into()),
false,
true,
);
match &expanded_key {
JsonLdTerm::Keyword(JsonLdKeyword::Value) => {
value_entry = Some(value.clone())
}
JsonLdTerm::Keyword(JsonLdKeyword::List) => {
if self
.active_property
.is_some_and(|p| p != JsonLdKeyword::Graph)
{
list_entry = Some((key.as_str(), value.clone()))
}
}
JsonLdTerm::Keyword(JsonLdKeyword::Set) => {
set_entry = Some((key.as_str(), value.clone()))
}
JsonLdTerm::Id(JsonLdLenient::Valid(Id::BlankId(id))) => {
env.warn(JsonLdWarning::BlankNodeIdProperty(id.clone()));
}
_ => (),
}
expanded_entries.push(ExpandedEntry {
key,
expanded_key,
value,
})
}
if let Some((list_key, list_value)) = list_entry {
let mut index = None;
for ExpandedEntry {
expanded_key,
value,
..
} in expanded_entries
{
match expanded_key {
JsonLdTerm::Keyword(JsonLdKeyword::Index) => match value.as_string() {
Some(value) => index = Some(value.to_string()),
None => return Err(JsonLdError::InvalidIndexValue.at(location)),
},
JsonLdTerm::Keyword(JsonLdKeyword::List) => (),
_ => return Err(JsonLdError::InvalidSetOrListObject.at(location)),
}
}
let mut result = Vec::new();
let list_value = JsonValue::force_as_array(&list_value);
let list_loc = location.object_value(list_key);
for (i, item) in list_value.iter().enumerate() {
let item_loc = list_loc.array_index(i);
let e = Box::pin(
self.with_active_context(&active_context)
.expand_element(env, item, false, item_loc),
)
.await?;
result.extend(e)
}
Ok(Expanded::Object(JsonLdIndexed::new(
JsonLdObject::List(JsonLdList::new(result)),
index,
)))
} else if let Some((set_key, set_value)) = set_entry {
for ExpandedEntry { expanded_key, .. } in expanded_entries {
match expanded_key {
JsonLdTerm::Keyword(JsonLdKeyword::Index) => {
}
JsonLdTerm::Keyword(JsonLdKeyword::Set) => (),
_ => return Err(JsonLdError::InvalidSetOrListObject.at(location)),
}
}
let set_loc = location.object_value(set_key);
Box::pin(
self.with_active_context(&active_context)
.expand_element(env, &set_value, false, set_loc),
)
.await
} else if let Some(value_entry) = value_entry {
let expanded_value = self.expand_value(
|w| env.warn(w),
input_type,
type_scoped_context,
expanded_entries,
&value_entry,
location,
)?;
if let Some(value) = expanded_value {
Ok(Expanded::Object(value))
} else {
Ok(Expanded::Null)
}
} else {
let e = self
.with_active_context(&active_context)
.expand_node(env, type_scoped_context, expanded_entries, location)
.await?;
if let Some(result) = e {
Ok(Expanded::Object(result.cast::<JsonLdObject>()))
} else {
Ok(Expanded::Null)
}
}
}
_ => {
if self
.active_property
.is_none_or(|p| p == JsonLdKeyword::Graph)
{
return Ok(Expanded::Null);
}
let active_context = if let Some(property_scoped_context) = property_scoped_context
{
let base_url = self
.active_property_definition()
.and_then(|definition| definition.base_url().map(ToOwned::to_owned));
let result = property_scoped_context
.process_with(
AsyncProcessingEnvironmentRef(env),
base_url.as_deref(),
self.active_context,
self.options.into(),
location,
)
.await?
.into_raw();
Mown::Owned(result)
} else {
Mown::Borrowed(self.active_context)
};
Ok(Expanded::Object(
self.with_active_context(&active_context).expand_literal(
|w| env.warn(w),
ExpandableLiteralValue::new(element),
location,
)?,
))
}
}
}
}