use crate::{Options, TypeLangValue};
use contextual::WithContext;
use json_ld_core::{
context::inverse::{LangSelection, Selection, TypeSelection},
object, Container, Context, Indexed, Nullable, Object, ProcessingMode, Term, Type, Value,
};
use json_ld_syntax::{is_keyword, is_keyword_like};
use rdf_types::Vocabulary;
use std::hash::Hash;
pub struct IriConfusedWithPrefix;
pub(crate) fn compact_iri<N>(
vocabulary: &N,
active_context: &Context<N::Iri, N::BlankId>,
var: &Term<N::Iri, N::BlankId>,
vocab: bool,
reverse: bool,
options: Options,
) -> Result<Option<String>, IriConfusedWithPrefix>
where
N: Vocabulary,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
{
compact_iri_full::<N, Object<N::Iri, N::BlankId>>(
vocabulary,
active_context,
var,
None,
vocab,
reverse,
options,
)
}
pub(crate) fn compact_key<N>(
vocabulary: &N,
active_context: &Context<N::Iri, N::BlankId>,
var: &Term<N::Iri, N::BlankId>,
vocab: bool,
reverse: bool,
options: Options,
) -> Result<Option<json_syntax::object::Key>, IriConfusedWithPrefix>
where
N: Vocabulary,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
{
Ok(compact_iri(vocabulary, active_context, var, vocab, reverse, options)?.map(Into::into))
}
pub(crate) fn compact_iri_with<N, O>(
vocabulary: &N,
active_context: &Context<N::Iri, N::BlankId>,
var: &Term<N::Iri, N::BlankId>,
value: &Indexed<O>,
vocab: bool,
reverse: bool,
options: Options,
) -> Result<Option<String>, IriConfusedWithPrefix>
where
N: Vocabulary,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
O: object::Any<N::Iri, N::BlankId>,
{
compact_iri_full(
vocabulary,
active_context,
var,
Some(value),
vocab,
reverse,
options,
)
}
pub(crate) fn compact_iri_full<N, O>(
vocabulary: &N,
active_context: &Context<N::Iri, N::BlankId>,
var: &Term<N::Iri, N::BlankId>,
value: Option<&Indexed<O>>,
vocab: bool,
reverse: bool,
options: Options,
) -> Result<Option<String>, IriConfusedWithPrefix>
where
N: Vocabulary,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
O: object::Any<N::Iri, N::BlankId>,
{
if var.is_null() {
return Ok(None);
}
if vocab {
if let Some(entry) = active_context.inverse().get(var) {
let mut containers = Vec::new();
let mut type_lang_value = None;
if let Some(value) = value {
if value.index().is_some() && !value.is_graph() {
containers.push(Container::Index);
containers.push(Container::IndexSet);
}
}
let mut has_index = false;
let mut is_simple_value = false;
if reverse {
type_lang_value = Some(TypeLangValue::Type(TypeSelection::Reverse));
containers.push(Container::Set);
} else {
let value_ref = value.map(|v| {
has_index = v.index().is_some();
v.inner().as_ref()
});
match value_ref {
Some(object::Ref::List(list)) => {
if !has_index {
containers.push(Container::List);
}
let mut common_type = None;
let mut common_lang_dir = None;
if list.is_empty() {
common_lang_dir = Some(Nullable::Some((
active_context.default_language(),
active_context.default_base_direction(),
)))
} else {
for item in list {
let mut item_type = None;
let mut item_lang_dir = None;
let mut is_value = false;
match item.inner() {
Object::Value(value) => {
is_value = true;
match value {
Value::LangString(lang_str) => {
item_lang_dir = Some(Nullable::Some((
lang_str.language(),
lang_str.direction(),
)))
}
Value::Literal(_, Some(ty)) => {
item_type = Some(Type::Iri(ty.clone()))
}
Value::Literal(_, None) => {
item_lang_dir = Some(Nullable::Null)
}
Value::Json(_) => item_type = Some(Type::Json),
}
}
_ => item_type = Some(Type::Id),
}
if common_lang_dir.is_none() {
common_lang_dir = item_lang_dir
} else if is_value && common_lang_dir != item_lang_dir {
common_lang_dir = Some(Nullable::Some((None, None)))
}
if common_type.is_none() {
common_type = Some(item_type)
} else if *common_type.as_ref().unwrap() != item_type {
common_type = Some(None)
}
if common_lang_dir == Some(Nullable::Some((None, None)))
&& common_type == Some(None)
{
break;
}
}
}
if common_lang_dir.is_none() {
common_lang_dir = Some(Nullable::Some((None, None)))
}
let common_lang_dir = common_lang_dir.unwrap();
if common_type.is_none() {
common_type = Some(None)
}
let common_type = common_type.unwrap();
if let Some(common_type) = common_type {
type_lang_value =
Some(TypeLangValue::Type(TypeSelection::Type(common_type)))
} else {
type_lang_value =
Some(TypeLangValue::Lang(LangSelection::Lang(common_lang_dir)))
}
}
Some(object::Ref::Node(node)) if node.is_graph() => {
if has_index {
containers.push(Container::GraphIndex);
containers.push(Container::GraphIndexSet);
}
if node.id.is_some() {
containers.push(Container::GraphId);
containers.push(Container::GraphIdSet);
}
containers.push(Container::Graph);
containers.push(Container::GraphSet);
containers.push(Container::Set);
if !has_index {
containers.push(Container::GraphIndex);
containers.push(Container::GraphIndexSet);
}
if node.id.is_none() {
containers.push(Container::GraphId);
containers.push(Container::GraphIdSet);
}
containers.push(Container::Index);
containers.push(Container::IndexSet);
type_lang_value = Some(TypeLangValue::Type(TypeSelection::Type(Type::Id)))
}
Some(object::Ref::Value(v)) => {
if (v.direction().is_some() || v.language().is_some()) && !has_index {
type_lang_value = Some(TypeLangValue::Lang(LangSelection::Lang(
Nullable::Some((v.language(), v.direction())),
)));
containers.push(Container::Language);
containers.push(Container::LanguageSet)
} else if let Some(ty) = v.typ() {
type_lang_value = Some(TypeLangValue::Type(TypeSelection::Type(
ty.as_syntax_type().cloned(),
)))
} else {
is_simple_value =
v.direction().is_none() && v.language().is_none() && !has_index
}
containers.push(Container::Set)
}
_ => {
type_lang_value = Some(TypeLangValue::Type(TypeSelection::Type(Type::Id)));
containers.push(Container::Id);
containers.push(Container::IdSet);
containers.push(Container::Type);
containers.push(Container::SetType);
containers.push(Container::Set)
}
}
}
containers.push(Container::None);
if options.processing_mode != ProcessingMode::JsonLd1_0 && !has_index {
containers.push(Container::Index);
containers.push(Container::IndexSet)
}
if options.processing_mode != ProcessingMode::JsonLd1_0 && is_simple_value {
containers.push(Container::Language);
containers.push(Container::LanguageSet)
}
let mut is_empty_list = false;
if let Some(value) = value {
if let object::Ref::List(list) = value.inner().as_ref() {
if list.is_empty() {
is_empty_list = true;
}
}
}
let selection = if is_empty_list {
Selection::Any
} else {
match type_lang_value {
Some(TypeLangValue::Type(type_value)) => {
let mut selection: Vec<TypeSelection<N::Iri>> = Vec::new();
if type_value == TypeSelection::Reverse {
selection.push(TypeSelection::Reverse);
}
let mut has_id_type = false;
if let Some(value) = value {
if let Some(id) = value.id() {
if type_value == TypeSelection::Type(Type::Id)
|| type_value == TypeSelection::Reverse
{
has_id_type = true;
let mut vocab = false;
let compacted_iri = compact_iri(
vocabulary,
active_context,
&id.clone().into_term(),
true,
false,
options,
)?
.unwrap();
if let Some(def) = active_context.get(compacted_iri.as_str()) {
if let Some(iri_mapping) = def.value() {
vocab = iri_mapping == id;
}
}
if vocab {
selection.push(TypeSelection::Type(Type::Vocab));
selection.push(TypeSelection::Type(Type::Id));
} else {
selection.push(TypeSelection::Type(Type::Id));
selection.push(TypeSelection::Type(Type::Vocab));
}
selection.push(TypeSelection::Type(Type::None));
}
}
}
if !has_id_type {
selection.push(type_value);
selection.push(TypeSelection::Type(Type::None));
}
selection.push(TypeSelection::Any);
Selection::Type(selection)
}
Some(TypeLangValue::Lang(lang_value)) => {
let mut selection = vec![
lang_value,
LangSelection::Lang(Nullable::Some((None, None))),
LangSelection::Any,
];
if let LangSelection::Lang(Nullable::Some((Some(_), Some(dir)))) =
lang_value
{
selection.push(LangSelection::Lang(Nullable::Some((None, Some(dir)))));
}
Selection::Lang(selection)
}
None => Selection::Lang(vec![
LangSelection::Lang(Nullable::Null),
LangSelection::Lang(Nullable::Some((None, None))),
LangSelection::Any,
]),
}
};
if let Some(term) = entry.select(&containers, &selection) {
return Ok(Some(term.to_string()));
}
}
if let Some(vocab_mapping) = active_context.vocabulary() {
if let Some(suffix) = var
.with(vocabulary)
.as_str()
.strip_prefix(vocab_mapping.with(vocabulary).as_str())
{
if !suffix.is_empty() && active_context.get(suffix).is_none() {
return Ok(Some(suffix.into()));
}
}
}
}
let mut compact_iri = String::new();
for binding in active_context.definitions() {
let key = binding.term();
let definition = binding.definition();
match definition.value() {
Some(iri_mapping) if definition.prefix() => {
if let Some(suffix) = var
.with(vocabulary)
.as_str()
.strip_prefix(iri_mapping.with(vocabulary).as_str())
{
if !suffix.is_empty() {
let mut candidate = key.to_string();
candidate.push(':');
candidate.push_str(suffix);
let candidate_def = active_context.get(candidate.as_str());
if (compact_iri.is_empty()
|| (candidate.len() <= compact_iri.len() && candidate < compact_iri))
&& (candidate_def.is_none()
|| (candidate_def.is_some()
&& (candidate_def.and_then(|def| def.value()) == Some(var))
&& value.is_none()))
{
compact_iri = candidate
}
}
}
}
_ => (),
}
}
if !compact_iri.is_empty() {
return Ok(Some(compact_iri.as_str().into()));
}
if let Some(iri) = var.as_iri() {
let iri = vocabulary.iri(iri).unwrap();
if active_context.contains_term(iri.scheme().as_str()) {
return Err(IriConfusedWithPrefix);
}
}
if !vocab {
if let Some(base_iri) = active_context.base_iri() {
let base_iri = vocabulary.iri(base_iri).unwrap();
if let Some(iri) = var.as_iri() {
let iri = vocabulary.iri(iri).unwrap();
return Ok(Some(disambiguate_keyword(
iri.relative_to(base_iri).as_str().into(),
)));
}
}
}
Ok(Some(var.with(vocabulary).to_string()))
}
fn disambiguate_keyword(s: String) -> String {
if is_keyword_like(&s) && !is_keyword(&s) {
"./".to_string() + &s
} else {
s
}
}