#[macro_use]
extern crate cfg_if;
extern crate serde_json as json;
cfg_if! {
if #[cfg(test)] {
extern crate colored;
mod test;
}
}
use json::Value;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::slice::Iter;
type Map = json::Map<String, Value>;
enum OneOrMany<'a> {
None,
One(&'a Value),
Many(Iter<'a, Value>),
}
impl<'a> From<&'a Value> for OneOrMany<'a> {
fn from(value: &'a Value) -> Self {
match *value {
Value::Array(ref arr) => OneOrMany::Many(arr.iter()),
ref value => OneOrMany::One(value),
}
}
}
impl<'a> Iterator for OneOrMany<'a> {
type Item = &'a Value;
fn next(&mut self) -> Option<&'a Value> {
match *self {
OneOrMany::None => None,
OneOrMany::One(value) => {
*self = OneOrMany::None;
Some(value)
},
OneOrMany::Many(ref mut iter) => {
iter.next()
},
}
}
}
#[derive(Clone,Debug,Default)]
pub struct Context {
pub ns: Option<String>,
pub lang: String,
pub prefixes: BTreeMap<String, String>,
pub aliases: BTreeMap<String, String>,
pub container: BTreeMap<String, String>,
}
impl Context {
pub fn new() -> Context {
Context::default()
}
pub fn merge_value(&mut self, value: &Value) {
for value in OneOrMany::from(value) {
match *value {
Value::Null => {
*self = Context::default();
},
Value::Object(ref object) => {
self.merge_object(object);
},
_ => {
},
}
}
}
pub fn merge_object(&mut self, object: &Map) {
for (key, value) in object {
if is_keyword(key) {
match key.as_str() {
"@vocab" => {
if let Some(ns) = value.as_str().filter(|s| is_absolute_iri(s)) {
self.ns = Some(ns.to_owned());
} else if value.is_null() {
self.ns = None;
}
},
"@language" => {
if let Some(lang) = value.as_str() {
self.lang = lang.to_owned();
} else if value.is_null() {
self.lang = "".to_owned();
}
},
_ => {},
}
} else {
match *value {
Value::String(ref string) => {
if is_curie_prefix(key) && is_absolute_iri(string) {
self.prefixes.insert(key.to_owned(), string.to_owned());
}
},
Value::Object(ref object) => {
let alias = object.get("@id")
.and_then(Value::as_str)
.filter(|string| !is_keyword(string));
if let Some(alias) = alias {
self.aliases.insert(key.to_owned(), alias.to_owned());
}
let container = object.get("@container")
.and_then(Value::as_str);
if let Some(container) = container {
self.container.insert(key.to_owned(), container.to_owned());
}
},
Value::Null => {
self.prefixes.remove(key);
self.aliases.remove(key);
self.container.remove(key);
},
_ => {},
}
}
}
}
pub fn expand_name<'a>(&self, name: &'a str) -> Option<Cow<'a, str>> {
if name.starts_with('@') {
return None;
}
let mut parts = name.splitn(2, ':');
let prefix = parts.next().unwrap();
if let Some(suffix) = parts.next() {
if let Some(base) = self.prefixes.get(prefix) {
Some(Cow::from(format!("{}{}", base, suffix)))
} else {
Some(Cow::from(name))
}
} else if let Some(ref base) = self.ns {
Some(Cow::from(format!("{}{}", base, name)))
} else {
None
}
}
}
impl<'a> From<&'a Value> for Context {
fn from(value: &'a Value) -> Context {
let mut context = Context::default();
context.merge_value(value);
context
}
}
impl<'a> From<&'a Map> for Context {
fn from(object: &'a Map) -> Context {
let mut context = Context::default();
context.merge_object(object);
context
}
}
#[derive(Clone,Debug,Default)]
pub struct TargetContext {
pub rules: Vec<(String, String)>,
}
impl TargetContext {
pub fn new() -> TargetContext {
TargetContext::default()
}
pub fn add_rule(&mut self, prefix: &str, base: &str) -> &mut Self {
self.rules.push((prefix.to_owned(), base.to_owned()));
self
}
pub fn compact_iri<'a>(&self, iri: &'a str) -> Cow<'a, str> {
for (prefix, base) in &self.rules {
if iri.starts_with(base) {
let suffix = &iri[base.len()..];
if prefix.is_empty() {
return Cow::from(suffix);
} else {
return Cow::from(format!("{}:{}", prefix, suffix));
}
}
}
Cow::from(iri)
}
}
#[derive(Clone,Debug,Default)]
pub struct Processor {
pub context: Context,
pub target: TargetContext,
}
impl Processor {
pub fn new() -> Processor {
Processor::default()
}
pub fn add_rule(&mut self, prefix: &str, base: &str) -> &mut Self {
self.target.add_rule(prefix, base);
self
}
pub fn process_value(&self, value: &Value) -> Value {
self.process_value_inner(value, &self.context)
}
pub fn process_object(&self, object: &Map) -> Map {
self.process_object_inner(object, &self.context)
}
fn process_value_inner(&self, value: &Value, context: &Context) -> Value {
match *value {
Value::Array(ref array) => {
let array = array.iter()
.map(|value| self.process_value_inner(value, context))
.collect::<Vec<_>>();
Value::Array(array)
},
Value::Object(ref object) => {
Value::Object(self.process_object_inner(object, context))
},
ref value => value.clone(),
}
}
fn process_object_inner(&self, object: &Map, context: &Context) -> Map {
let local_context = object.get("@context").map(|value| {
let mut context = context.clone();
context.merge_value(value);
context
});
let context = local_context.as_ref().unwrap_or(context);
let mut result = Map::with_capacity(object.len());
for (key, value) in object {
if key.starts_with('@') {
match key.as_str() {
"@id" => {
if let Some(iri) = value.as_str().filter(|s| is_absolute_iri(s)) {
result.insert(key.clone(), Value::String(iri.to_owned()));
}
},
"@type" => {
let value = OneOrMany::from(value)
.filter_map(|value| value.as_str())
.filter_map(|string| context.expand_name(string))
.map(|iri| self.target.compact_iri(&iri).into_owned())
.map(Value::String)
.collect::<Vec<_>>();
if !value.is_empty() {
result.insert(key.clone(), Value::Array(value));
}
},
_ => {
},
}
continue;
}
let resolved = context.aliases.get(key).map(String::as_str).unwrap_or(key);
let resolved = match context.expand_name(resolved) {
Some(iri) => self.target.compact_iri(&iri).into_owned(),
None => continue,
};
result.insert(resolved, match context.container.get(key).map(String::as_str) {
Some("@language") => {
match *value {
Value::String(_) => {
let mut object = Map::with_capacity(1);
object.insert(context.lang.clone(), value.clone());
Value::Object(object)
},
Value::Object(ref object) => {
let object = object.iter()
.filter(|(_, value)| value.is_string())
.map(|(key, value)| (key.clone(), value.clone()))
.collect();
Value::Object(object)
},
_ => {
continue;
},
}
},
_ => {
self.process_value_inner(value, context)
},
});
}
result
}
}
fn is_keyword(input: &str) -> bool {
input.starts_with('@')
}
fn is_absolute_iri(input: &str) -> bool {
input.contains(':') && !input.starts_with('@')
}
fn is_curie_prefix(input: &str) -> bool {
!input.is_empty() && !input.contains(':') && !input.starts_with('@')
}