#[cfg(feature = "async")]
use json_event_parser::TokioAsyncWriterJsonSerializer;
use json_event_parser::{JsonEvent, WriterJsonSerializer};
use crate::model::node::NamedOrBlankNodeRef;
use crate::model::quad::GraphNameRef;
use crate::model::triple::{ObjectRef, PredicateRef, SubjectRef};
use crate::model::*;
use crate::optimization::TermRef;
use crate::vocab::xsd;
use oxiri::{Iri, IriParseError};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::io;
use std::io::Write;
#[cfg(feature = "async")]
use tokio::io::AsyncWrite;
#[derive(Default, Clone)]
#[must_use]
pub struct JsonLdSerializer {
prefixes: BTreeMap<String, String>,
base_iri: Option<Iri<String>>,
}
impl JsonLdSerializer {
#[inline]
pub fn new() -> Self {
Self {
prefixes: BTreeMap::new(),
base_iri: None,
}
}
#[inline]
pub fn with_prefix(
mut self,
prefix_name: impl Into<String>,
prefix_iri: impl Into<String>,
) -> Result<Self, IriParseError> {
self.prefixes.insert(
prefix_name.into(),
Iri::parse(prefix_iri.into())?.into_inner(),
);
Ok(self)
}
#[inline]
pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
self.base_iri = Some(Iri::parse(base_iri.into())?);
Ok(self)
}
pub fn for_writer<W: Write>(self, writer: W) -> WriterJsonLdSerializer<W> {
WriterJsonLdSerializer {
writer: WriterJsonSerializer::new(writer),
inner: self.inner_writer(),
}
}
#[cfg(feature = "async")]
pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
self,
writer: W,
) -> TokioAsyncWriterJsonLdSerializer<W> {
TokioAsyncWriterJsonLdSerializer {
writer: TokioAsyncWriterJsonSerializer::new(writer),
inner: self.inner_writer(),
}
}
fn inner_writer(self) -> InnerJsonLdWriter {
InnerJsonLdWriter {
started: false,
current_graph_name: None,
current_subject: None,
current_predicate: None,
emitted_predicates: BTreeSet::new(),
prefixes: self.prefixes,
base_iri: self.base_iri,
}
}
}
#[must_use]
pub struct WriterJsonLdSerializer<W: Write> {
writer: WriterJsonSerializer<W>,
inner: InnerJsonLdWriter,
}
impl<W: Write> WriterJsonLdSerializer<W> {
pub fn serialize_quad<'a>(&mut self, t: impl Into<QuadRef<'a>>) -> io::Result<()> {
let mut buffer = Vec::new();
self.inner.serialize_quad(t, &mut buffer)?;
self.flush_buffer(&mut buffer)
}
pub fn finish(mut self) -> io::Result<W> {
let mut buffer = Vec::new();
self.inner.finish(&mut buffer);
self.flush_buffer(&mut buffer)?;
self.writer.finish()
}
fn flush_buffer(&mut self, buffer: &mut Vec<JsonEvent<'_>>) -> io::Result<()> {
for event in buffer.drain(0..) {
self.writer.serialize_event(event)?;
}
Ok(())
}
}
#[cfg(feature = "async")]
#[must_use]
pub struct TokioAsyncWriterJsonLdSerializer<W: AsyncWrite + Unpin> {
writer: TokioAsyncWriterJsonSerializer<W>,
inner: InnerJsonLdWriter,
}
#[cfg(feature = "async")]
impl<W: AsyncWrite + Unpin> TokioAsyncWriterJsonLdSerializer<W> {
pub async fn serialize_quad<'a>(&mut self, t: impl Into<QuadRef<'a>>) -> io::Result<()> {
let mut buffer = Vec::new();
self.inner.serialize_quad(t, &mut buffer)?;
self.flush_buffer(&mut buffer).await
}
pub async fn finish(mut self) -> io::Result<W> {
let mut buffer = Vec::new();
self.inner.finish(&mut buffer);
self.flush_buffer(&mut buffer).await?;
self.writer.finish()
}
async fn flush_buffer(&mut self, buffer: &mut Vec<JsonEvent<'_>>) -> io::Result<()> {
for event in buffer.drain(0..) {
self.writer.serialize_event(event).await?;
}
Ok(())
}
}
pub struct InnerJsonLdWriter {
started: bool,
current_graph_name: Option<GraphName>,
current_subject: Option<NamedOrBlankNode>,
current_predicate: Option<NamedNode>,
emitted_predicates: BTreeSet<String>,
prefixes: BTreeMap<String, String>,
base_iri: Option<Iri<String>>,
}
impl InnerJsonLdWriter {
fn serialize_quad<'a>(
&mut self,
quad: impl Into<QuadRef<'a>>,
output: &mut Vec<JsonEvent<'a>>,
) -> io::Result<()> {
if !self.started {
self.serialize_start(output);
self.started = true;
}
let quad = quad.into();
if self
.current_graph_name
.as_ref()
.is_some_and(|graph_name| graph_name != &quad.graph_name().to_owned())
{
output.push(JsonEvent::EndArray);
output.push(JsonEvent::EndObject);
if self
.current_graph_name
.as_ref()
.is_some_and(|g| !g.is_default_graph())
{
output.push(JsonEvent::EndArray);
output.push(JsonEvent::EndObject);
}
self.current_graph_name = None;
self.current_subject = None;
self.current_predicate = None;
self.emitted_predicates.clear();
} else if self.current_subject.as_ref().is_some_and(|subject| {
match quad.subject() {
SubjectRef::NamedNode(n) => subject != &NamedOrBlankNode::NamedNode(n.to_owned()),
SubjectRef::BlankNode(b) => subject != &NamedOrBlankNode::BlankNode(b.to_owned()),
_ => true, }
}) || self.current_predicate.as_ref().is_some_and(|predicate| {
match quad.predicate() {
PredicateRef::NamedNode(n) => predicate != &n.to_owned(),
_ => true, }
}) && {
let pred_str = match quad.predicate() {
PredicateRef::NamedNode(n) => n.as_str(),
PredicateRef::Variable(v) => v.as_str(),
};
self.emitted_predicates.contains(pred_str)
} {
output.push(JsonEvent::EndArray);
output.push(JsonEvent::EndObject);
self.current_subject = None;
self.emitted_predicates.clear();
self.current_predicate = None;
} else if self.current_predicate.as_ref().is_some_and(|predicate| {
match quad.predicate() {
PredicateRef::NamedNode(n) => predicate != &n.to_owned(),
_ => true, }
}) {
output.push(JsonEvent::EndArray);
if let Some(current_predicate) = self.current_predicate.take() {
self.emitted_predicates
.insert(current_predicate.into_string());
}
}
if self.current_graph_name.is_none() {
if !quad.graph_name().is_default_graph() {
output.push(JsonEvent::StartObject);
output.push(JsonEvent::ObjectKey("@id".into()));
output.push(JsonEvent::String(self.id_value(match quad.graph_name() {
GraphNameRef::NamedNode(iri) => NamedOrBlankNodeRef::NamedNode(iri),
GraphNameRef::BlankNode(bnode) => NamedOrBlankNodeRef::BlankNode(bnode),
GraphNameRef::DefaultGraph => unreachable!(),
GraphNameRef::Variable(_) => unreachable!(), })));
output.push(JsonEvent::ObjectKey("@graph".into()));
output.push(JsonEvent::StartArray);
}
self.current_graph_name = Some(quad.graph_name().to_owned());
}
if self.current_subject.is_none() {
output.push(JsonEvent::StartObject);
output.push(JsonEvent::ObjectKey("@id".into()));
#[allow(
unreachable_patterns,
clippy::match_wildcard_for_single_variants,
clippy::allow_attributes
)]
output.push(JsonEvent::String(self.id_value(match quad.subject() {
SubjectRef::NamedNode(iri) => NamedOrBlankNodeRef::NamedNode(iri),
SubjectRef::BlankNode(bnode) => NamedOrBlankNodeRef::BlankNode(bnode),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"JSON-LD does not support RDF 1.2 yet",
));
}
})));
self.current_subject = Some(match quad.subject() {
SubjectRef::NamedNode(n) => NamedOrBlankNode::NamedNode(n.to_owned()),
SubjectRef::BlankNode(b) => NamedOrBlankNode::BlankNode(b.to_owned()),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"JSON-LD does not support variables or quoted triples as subjects",
));
}
});
}
if self.current_predicate.is_none() {
let predicate_str = match quad.predicate() {
PredicateRef::NamedNode(n) => {
self.compact_iri(n.as_str())
}
PredicateRef::Variable(v) => Cow::Borrowed(v.as_str()),
};
output.push(JsonEvent::ObjectKey(predicate_str));
output.push(JsonEvent::StartArray);
self.current_predicate = Some(match quad.predicate() {
PredicateRef::NamedNode(n) => n.to_owned(),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"JSON-LD does not support variables as predicates",
));
}
});
}
let object_ref = match quad.object() {
ObjectRef::NamedNode(n) => TermRef::NamedNode(n.as_str()),
ObjectRef::BlankNode(b) => TermRef::BlankNode(b.as_str()),
ObjectRef::Literal(l) => TermRef::from_literal(l),
ObjectRef::Variable(v) => TermRef::Variable(v.as_str()),
};
self.serialize_term(object_ref, output)
}
fn serialize_start(&self, output: &mut Vec<JsonEvent<'_>>) {
if self.base_iri.is_some() || !self.prefixes.is_empty() {
output.push(JsonEvent::StartObject);
output.push(JsonEvent::ObjectKey("@context".into()));
output.push(JsonEvent::StartObject);
if let Some(base_iri) = &self.base_iri {
output.push(JsonEvent::ObjectKey("@base".into()));
output.push(JsonEvent::String(base_iri.to_string().into()));
}
for (prefix_name, prefix_iri) in &self.prefixes {
output.push(JsonEvent::ObjectKey(if prefix_name.is_empty() {
"@vocab".into()
} else {
prefix_name.clone().into()
}));
output.push(JsonEvent::String(prefix_iri.clone().into()));
}
output.push(JsonEvent::EndObject);
output.push(JsonEvent::ObjectKey("@graph".into()));
}
output.push(JsonEvent::StartArray);
}
fn serialize_term<'a>(
&self,
term: TermRef<'a>,
output: &mut Vec<JsonEvent<'a>>,
) -> io::Result<()> {
output.push(JsonEvent::StartObject);
#[allow(
unreachable_patterns,
clippy::match_wildcard_for_single_variants,
clippy::allow_attributes
)]
match term {
TermRef::NamedNode(iri) => {
output.push(JsonEvent::ObjectKey("@id".into()));
output.push(JsonEvent::String(self.id_value_from_str(iri, false)));
}
TermRef::BlankNode(bnode) => {
output.push(JsonEvent::ObjectKey("@id".into()));
output.push(JsonEvent::String(self.id_value_from_str(bnode, true)));
}
TermRef::Literal(value, datatype, language) => {
if let Some(lang) = language {
output.push(JsonEvent::ObjectKey("@language".into()));
output.push(JsonEvent::String(lang.into()));
} else if let Some(dt) = datatype {
if dt != xsd::STRING.as_str() {
output.push(JsonEvent::ObjectKey("@type".into()));
output.push(JsonEvent::String(dt.into()));
}
}
output.push(JsonEvent::ObjectKey("@value".into()));
output.push(JsonEvent::String(value.into()));
}
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"JSON-LD does not support RDF 1.2 yet",
));
}
}
output.push(JsonEvent::EndObject);
Ok(())
}
fn id_value<'a>(&self, id: NamedOrBlankNodeRef<'a>) -> Cow<'a, str> {
match id {
NamedOrBlankNodeRef::NamedNode(iri) => {
if let Some(base_iri) = &self.base_iri {
if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(iri.as_str())) {
let relative = relative.into_inner();
if !relative.split_once(':').is_some_and(|(prefix, suffix)| {
prefix == "_" || suffix.starts_with("//")
}) {
return relative.into();
}
}
}
iri.as_str().into()
}
NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
}
}
fn id_value_from_str<'a>(&self, id: &'a str, is_blank_node: bool) -> Cow<'a, str> {
if is_blank_node {
format!("_:{id}").into()
} else {
if let Some(base_iri) = &self.base_iri {
if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(id)) {
let relative = relative.into_inner();
if !relative
.split_once(':')
.is_some_and(|(prefix, suffix)| prefix == "_" || suffix.starts_with("//"))
{
return relative.into();
}
}
}
id.into()
}
}
#[allow(dead_code)]
fn type_value(id: NamedOrBlankNodeRef<'_>) -> Cow<'_, str> {
match id {
NamedOrBlankNodeRef::NamedNode(iri) => iri.as_str().into(),
NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
}
}
fn compact_iri<'a>(&self, iri: &'a str) -> Cow<'a, str> {
for (prefix, namespace) in &self.prefixes {
if let Some(local_name) = iri.strip_prefix(namespace.as_str()) {
return Cow::Owned(format!("{prefix}:{local_name}"));
}
}
Cow::Borrowed(iri)
}
fn finish(&mut self, output: &mut Vec<JsonEvent<'static>>) {
if !self.started {
self.serialize_start(output);
}
if self.current_predicate.is_some() {
output.push(JsonEvent::EndArray)
}
if self.current_subject.is_some() {
output.push(JsonEvent::EndObject)
}
if self
.current_graph_name
.as_ref()
.is_some_and(|g| !g.is_default_graph())
{
output.push(JsonEvent::EndArray);
output.push(JsonEvent::EndObject)
}
output.push(JsonEvent::EndArray);
if self.base_iri.is_some() || !self.prefixes.is_empty() {
output.push(JsonEvent::EndObject);
}
}
}