use crate::{common::indenter::Indenter, GraphWriter};
use itertools::Itertools;
use objio::{impl_has_options, HasOptions, ObjectWriter};
use rdftk_core::{
error::{Error, Result},
model::{
graph::Graph,
literal::{DataType, Literal},
statement::{Collection, ObjectNode, Statement, SubjectNode},
},
};
use rdftk_iri::Iri;
use rdftk_names::{dc::elements, foaf, owl, rdf, rdfs, skos};
use std::{
cell::RefCell,
collections::{BTreeMap, HashSet},
io::Write,
str::FromStr,
sync::LazyLock,
};
use tracing::trace;
#[derive(Clone, Debug)]
pub struct TurtleWriterOptions {
id_base: Option<Iri>,
nest_blank_nodes: bool,
outdent_blank_node_end: bool,
outdent_collection_end: bool,
use_rdf_type_a: bool,
use_sparql_style: bool,
use_intellij_style: bool,
place_type_on_subject_line: bool,
convert_to_id_base: Option<Iri>,
convert_base: Vec<(Iri, Iri)>,
indent_width: usize,
predicate_padding: bool,
}
#[derive(Clone, Debug, Default)]
pub struct TurtleWriter {
options: TurtleWriterOptions,
context: RefCell<WriterContext>,
}
#[derive(Clone, Copy, Debug, Default)]
struct WriterStatusFlags {
is_next_object_blank: bool,
is_being_sorted: bool,
is_last_of_subject: bool,
is_last_of_predicate: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum PredicateGroupOrdering {
Type,
Label,
Comment,
Other,
}
#[derive(Clone, Debug, Default)]
struct WriterContext {
indenter: Indenter,
blanks_to_write: HashSet<SubjectNode>,
}
const DECL_BASE_TTL: &str = "@base";
const DECL_BASE_SPARQL: &str = "BASE";
const DECL_PREFIX_TTL: &str = "@prefix";
const DECL_PREFIX_SPARQL: &str = "PREFIX";
const NAME_SEPARATOR: &str = ":";
const IRI_START: &str = "<";
const IRI_END: &str = ">";
const BLANK_NODE_PREFIX: &str = "_";
const BLANK_NODE_START: &str = "[";
const BLANK_NODE_END: &str = "]";
const COLLECTION_START: &str = "(";
const COLLECTION_END: &str = ")";
const LANGUAGE_PREFIX: &str = "@";
const DATATYPE_PREFIX: &str = "^^";
const PREDICATE_SEPARATOR: &str = " ;";
const OBJECT_SEPARATOR: &str = ",";
const SPACE_SEPARATOR: &str = " ";
const END_OF_STATEMENT: &str = " .";
const END_OF_LINE: &str = "\n";
const RDF_TYPE_A: &str = "a";
impl Default for TurtleWriterOptions {
fn default() -> Self {
Self {
id_base: None,
nest_blank_nodes: true,
outdent_blank_node_end: false,
outdent_collection_end: false,
use_rdf_type_a: false,
use_sparql_style: false,
use_intellij_style: false,
place_type_on_subject_line: false,
convert_to_id_base: None,
convert_base: Vec::new(),
indent_width: 4,
predicate_padding: false,
}
}
}
impl TurtleWriterOptions {
pub fn with_id_base(self, id_base: Iri) -> Self {
Self {
id_base: Some(id_base.clone()),
..self
}
}
pub fn with_intellij_style(self) -> Self {
Self {
use_intellij_style: true,
indent_width: 4,
..self
}
}
pub fn with_sparql_style(self) -> Self {
Self {
use_sparql_style: true,
..self
}
}
pub fn with_indent_width(self, indent_width: usize) -> Self {
Self {
indent_width,
..self
}
}
pub fn with_predicate_padding(self, predicate_padding: bool) -> Self {
Self {
predicate_padding,
..self
}
}
pub fn with_nested_blank_nodes(self) -> Self {
Self {
nest_blank_nodes: true,
..self
}
}
pub fn without_nested_blank_nodes(self) -> Self {
Self {
nest_blank_nodes: false,
..self
}
}
pub fn id_base(&self) -> Option<&Iri> {
self.id_base.as_ref()
}
pub fn set_id_base(&mut self, id_base: Iri) {
self.id_base = Some(id_base);
}
pub fn unset_id_base(&mut self) {
self.id_base = None;
}
pub fn nest_blank_nodes(&self) -> bool {
self.nest_blank_nodes
}
pub fn set_nest_blank_nodes(&mut self, nest_blank_nodes: bool) {
self.nest_blank_nodes = nest_blank_nodes;
}
pub fn use_sparql_style(&self) -> bool {
self.use_sparql_style
}
pub fn set_use_sparql_style(&mut self, use_sparql_style: bool) {
self.use_sparql_style = use_sparql_style;
}
pub fn use_intellij_style(&self) -> bool {
self.use_intellij_style
}
pub fn set_use_intellij_style(&mut self, use_intellij_style: bool) {
self.use_intellij_style = use_intellij_style;
}
pub fn place_type_on_subject_line(&self) -> bool {
self.place_type_on_subject_line
}
pub fn set_place_type_on_subject_line(&mut self, place_type_on_subject_line: bool) {
self.place_type_on_subject_line = place_type_on_subject_line;
}
pub fn convert_to_id_base(&self) -> Option<&Iri> {
self.convert_to_id_base.as_ref()
}
pub fn set_convert_to_id_base(&mut self, convert_to_id_base: Iri) {
self.convert_to_id_base = Some(convert_to_id_base);
}
pub fn unset_convert_to_id_base(&mut self) {
self.convert_to_id_base = None;
}
pub fn convert_base(&self) -> &Vec<(Iri, Iri)> {
&self.convert_base
}
pub fn indent_width(&self) -> usize {
self.indent_width
}
pub fn set_indent_width(&mut self, indent_width: usize) {
assert!(indent_width > 0);
self.indent_width = indent_width;
}
pub fn predicate_padding(&self) -> bool {
self.predicate_padding
}
pub fn set_predicate_padding(&mut self, predicate_padding: bool) {
self.predicate_padding = predicate_padding;
}
}
impl_has_options!(TurtleWriter, TurtleWriterOptions);
impl TurtleWriter {
pub fn with_options(self, options: TurtleWriterOptions) -> Self {
let mut self_mut = self;
self_mut.set_options(options);
self_mut
}
}
impl ObjectWriter<Graph> for TurtleWriter {
type Error = Error;
fn write<W>(&self, w: &mut W, graph: &Graph) -> Result<()>
where
W: Write,
{
let (nested, plain): (Vec<_>, Vec<_>) =
graph.statements().partition(|stmt| stmt.is_nested());
let mut reified_statements: Vec<Statement> = nested
.into_iter()
.map(|stmt| {
let (subject, mut new) = stmt.reify().unwrap();
new.push(Statement::new(
stmt.subject(),
stmt.predicate().clone(),
subject.to_object(),
));
new
})
.flatten()
.collect();
plain
.into_iter()
.for_each(|s| reified_statements.push(s.clone()));
let mut denested_graph = Graph::from(reified_statements);
denested_graph.set_prefix_mappings(graph.prefix_mappings().clone());
self.write_turtle_doc(w, &denested_graph)
}
}
impl GraphWriter for TurtleWriter {}
impl TurtleWriter {
#[inline(always)]
fn indent(&self) {
let context = self.context.borrow();
context.indenter.indent();
}
#[inline(always)]
fn outdent(&self) {
let context = self.context.borrow();
context.indenter.outdent();
}
#[inline(always)]
fn new_line<W: Write>(&self, w: &mut W, flags: WriterStatusFlags) -> Result<()> {
trace!(name: "new_line", ?flags);
if flags.is_being_sorted {
Ok(write!(w, "{SPACE_SEPARATOR}")?)
} else {
let context = self.context.borrow();
write!(w, "{END_OF_LINE}{}", context.indenter)?;
Ok(())
}
}
#[inline(always)]
fn write_padded<W: Write>(&self, w: &mut W, value: &str, max_len: usize) -> Result<()> {
trace!(name: "write_padded", value, max_len);
Ok(if max_len == 0 {
write!(w, "{}{}", value, SPACE_SEPARATOR)
} else {
write!(w, "{:<max_len$}", value)
}?)
}
#[inline(always)]
fn write_padding<W: Write>(&self, w: &mut W, max_len: usize) -> Result<()> {
trace!(name: "write_padding", max_len);
Ok(if max_len == 0 {
write!(w, " ")
} else {
write!(w, "{:max_len$}", SPACE_SEPARATOR)
}?)
}
fn sorted_subjects(&self, graph: &Graph) -> Vec<SubjectNode> {
trace!("sorted_subjects");
graph
.node_subjects()
.into_iter()
.sorted()
.cloned()
.collect::<Vec<SubjectNode>>()
}
fn max_len_predicates(&self, graph: &Graph, predicates: &[&Iri]) -> Result<usize> {
trace!("max_len_predicates");
let all_predicates_as_strings = predicates
.iter()
.map(|iri| self.compress_iri(graph, iri))
.collect::<Result<Vec<String>>>()?
.iter()
.fold(0, |a, b| a.max(b.len()));
Ok(all_predicates_as_strings)
}
fn object_sort_key(&self, graph: &Graph, object: &ObjectNode) -> Result<String> {
trace!("object_sort_key");
let mut buffer = Vec::<u8>::new();
let new_writer = Self::default().with_options(self.options.clone());
let flags = WriterStatusFlags {
is_being_sorted: true,
..Default::default()
};
new_writer.write_object(&mut buffer, graph, object, flags)?;
Ok(String::from_utf8(buffer)?)
}
fn write_turtle_doc<W: Write>(&self, w: &mut W, graph: &Graph) -> Result<()> {
trace!("write_turtle_doc");
{
let mut context_mut = self.context.borrow_mut();
context_mut.indenter =
Indenter::default().with_default_indent_width(self.options.indent_width());
context_mut.blanks_to_write = graph
.blank_node_subjects()
.iter()
.map(|s| {
let s = *s;
s.clone()
})
.collect();
}
self.write_base_iri(w)?;
self.write_prefixes(w, graph)?;
let flags = WriterStatusFlags::default();
self.write_triples(w, graph, flags)?;
Ok(())
}
fn write_base_iri<W: Write>(&self, w: &mut W) -> Result<()> {
trace!("write_base_iri");
if let Some(base) = &self.options.id_base() {
let (decl, eos) =
if self.options.use_sparql_style() && !self.options.use_intellij_style() {
(DECL_BASE_SPARQL, "")
} else {
(DECL_BASE_TTL, END_OF_STATEMENT)
};
writeln!(
w,
"{decl} {IRI_START}{}{IRI_END}{eos}",
base.to_string().as_str()
)?;
if !self.options.use_intellij_style() {
writeln!(w)?;
}
}
Ok(())
}
fn write_prefixes<W: Write>(&self, w: &mut W, graph: &Graph) -> Result<()> {
trace!("write_prefixes");
let mappings = graph.prefix_mappings();
if !mappings.is_empty() {
for (prefix, namespace) in mappings.mappings().sorted() {
let prefix = prefix.as_ref().map(|n| n.as_ref()).unwrap_or("");
let mut namespace_str = namespace.to_string();
for (from_base, to_base) in self.options.convert_base().iter() {
let from_base_str = from_base.to_string();
if namespace_str.starts_with(from_base_str.as_str()) {
namespace_str = format!(
"{}{}",
to_base.to_string().as_str(),
&namespace_str[from_base_str.len()..]
);
break;
}
}
trace!("write_prefixes {prefix}: {IRI_START}{namespace_str}{IRI_END}");
let (decl, eos) =
if self.options.use_sparql_style() && !self.options.use_intellij_style() {
(DECL_PREFIX_SPARQL, "")
} else {
(DECL_PREFIX_TTL, END_OF_STATEMENT)
};
writeln!(
w,
"{decl} {prefix}{NAME_SEPARATOR} {IRI_START}{namespace_str}{IRI_END}{eos}"
)?;
}
writeln!(w)?;
}
Ok(())
}
fn write_triples<W: Write>(
&self,
w: &mut W,
graph: &Graph,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_triples", ?flags);
for subject in self.sorted_subjects(graph) {
self.write_subject(w, graph, &subject, flags)?;
self.write_predicate_object_list(w, graph, &subject, flags)?;
writeln!(w)?;
}
let context = self.context.borrow();
for subject in context.blanks_to_write.iter() {
context.indenter.reset_depth();
self.write_subject(w, graph, subject, flags)?;
self.write_predicate_object_list(w, graph, subject, flags)?;
writeln!(w)?;
}
Ok(())
}
fn write_subject<W: Write>(
&self,
w: &mut W,
graph: &Graph,
subject: &SubjectNode,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_subject", ?subject, ?flags);
let at_start_of_line = self.context.borrow().indenter.is_not_indented();
match (subject, at_start_of_line) {
(SubjectNode::Blank(blank), true) => {
let initial = if flags.is_being_sorted {
SPACE_SEPARATOR
} else {
END_OF_LINE
};
write!(w, "{initial}{BLANK_NODE_PREFIX}{NAME_SEPARATOR}{blank}")?;
}
(SubjectNode::Resource(_), _) => {
self.write_iri(w, graph, subject.as_resource().unwrap())?;
}
(SubjectNode::Statement(_), _) => {
unreachable!("RDF-* Statements are not supported in Turtle representation")
}
_ => {}
}
self.indent();
Ok(())
}
fn write_predicate_object_list<W: Write>(
&self,
w: &mut W,
graph: &Graph,
subject: &SubjectNode,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_predicate_object_list", ?subject, ?flags);
let all_predicates = Vec::from_iter(graph.predicates_for(subject));
let mut count = 0;
let total_number = all_predicates.len();
let max_len = if self.options.predicate_padding {
0
} else {
1 + self.max_len_predicates(graph, &all_predicates)?
};
for (group, ref mut preds) in PredicateGroupOrdering::group_predicates(&all_predicates) {
preds.sort_by_cached_key(|iri| self.compress_iri(graph, iri).unwrap());
for predicate in preds {
count += 1;
let flags = WriterStatusFlags {
is_last_of_subject: count == total_number,
..flags
};
self.write_predicate_object(w, graph, group, subject, predicate, max_len, flags)?;
}
}
Ok(())
}
fn write_verb<W: Write>(
&self,
w: &mut W,
graph: &Graph,
group: PredicateGroupOrdering,
predicate: &Iri,
max_len: usize,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_verb", ?predicate, ?group, max_len, ?flags);
if group == PredicateGroupOrdering::Type {
let type_verb = if self.options.use_rdf_type_a {
RDF_TYPE_A.to_string()
} else {
self.compress_iri(graph, rdf::a_type())?
};
return if self.options.place_type_on_subject_line() {
Ok(write!(w, " {type_verb} ")?)
} else {
self.new_line(w, flags)?;
self.write_padded(w, &type_verb, max_len)
};
}
self.new_line(w, flags)?;
let pred = self.compress_iri(graph, predicate)?;
self.write_padded(w, pred.as_str(), max_len)
}
#[inline(always)]
fn write_iri<W: Write>(&self, w: &mut W, graph: &Graph, iri: &Iri) -> Result<()> {
trace!(name: "write_iri", ?iri);
Ok(write!(w, "{}", self.compress_iri(graph, iri)?)?)
}
fn compress_iri(&self, graph: &Graph, iri: &Iri) -> Result<String> {
trace!(name: "compress_iri", ?iri);
let mut iri_str = iri.to_string();
if let Some(id_base) = &self.options.id_base() {
if let Some(ref convert_to_id_base) = self.options.convert_to_id_base() {
let target_id_base = convert_to_id_base.to_string();
if iri_str.starts_with(target_id_base.as_str()) {
return Ok(format!(
"{IRI_START}{}{IRI_END}",
&iri_str[target_id_base.len()..]
));
}
}
let id_base_str = id_base.to_string();
if iri_str.starts_with(id_base_str.as_str()) {
return Ok(format!(
"{IRI_START}{}{IRI_END}",
&iri_str[id_base_str.len()..]
));
}
}
for (from_base, to_base) in self.options.convert_base().iter() {
let from_base_str = from_base.to_string();
if iri_str.starts_with(from_base_str.as_str()) {
iri_str = format!(
"{}{}",
to_base.to_string().as_str(),
&iri_str[from_base_str.len()..]
);
}
}
let iri = Iri::from_str(iri_str.as_str())?;
Ok(match graph.prefix_mappings().compress(&iri) {
None => format!("{IRI_START}{iri}{IRI_END}"),
Some(_qname) => format!("{_qname}"),
})
}
fn write_literal<W: Write>(&self, w: &mut W, graph: &Graph, literal: &Literal) -> Result<()> {
trace!(name: "write_literal", ?literal);
Ok(match literal.data_type() {
Some(DataType::Iri) => {
let iri = Iri::parse(literal.lexical_form())?;
self.write_iri(w, graph, &iri)?
}
Some(DataType::Boolean)
| Some(DataType::Long)
| Some(DataType::Int)
| Some(DataType::Short)
| Some(DataType::Byte)
| Some(DataType::UnsignedLong)
| Some(DataType::UnsignedInt)
| Some(DataType::UnsignedShort)
| Some(DataType::UnsignedByte)
| Some(DataType::Float)
| Some(DataType::Double)
| Some(DataType::Decimal) => write!(w, "{}", literal.lexical_form())?,
_ => {
write!(w, "{:?}", literal.lexical_form())?;
match (literal.data_type(), literal.language()) {
(Some(data_type), None) => {
write!(w, "{DATATYPE_PREFIX}")?;
let iri = data_type.as_iri();
self.write_iri(w, graph, iri)?;
}
(None, Some(language)) => write!(w, "{LANGUAGE_PREFIX}{}", language)?,
_ => (),
}
}
})
}
fn write_predicate_object_object<W: Write>(
&self,
w: &mut W,
graph: &Graph,
object: &ObjectNode,
max_len: usize,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_predicate_object_object", ?object, max_len, ?flags);
self.write_object(w, graph, object, flags)?;
if flags.is_last_of_predicate {
if flags.is_last_of_subject {
self.outdent();
}
if self.context.borrow().indenter.is_not_indented() {
write!(w, "{END_OF_STATEMENT}")?;
self.new_line(w, flags)?;
} else if !flags.is_last_of_subject {
write!(w, "{PREDICATE_SEPARATOR}")?;
}
} else {
write!(w, "{OBJECT_SEPARATOR}")?;
if !flags.is_next_object_blank {
self.indent();
self.new_line(w, flags)?;
if max_len > 0 {
self.write_padding(w, max_len)?;
}
self.outdent();
}
}
Ok(())
}
fn write_object<W: Write>(
&self,
w: &mut W,
graph: &Graph,
object: &ObjectNode,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_object", ?object, ?flags);
match &object {
ObjectNode::Blank(blank) => {
if self.options.nest_blank_nodes()
&& graph.contains_subject(&object.to_subject().unwrap())
{
self.write_blank_node_property_list(w, graph, object, flags)?;
} else if self.options.nest_blank_nodes()
&& !graph.contains_subject(&object.to_subject().unwrap())
{
write!(w, "{BLANK_NODE_START}{BLANK_NODE_END}")?;
} else {
write!(w, "{BLANK_NODE_PREFIX}{NAME_SEPARATOR}{blank}",)?;
}
}
ObjectNode::Resource(iri) => {
self.write_iri(w, graph, iri)?;
}
ObjectNode::Literal(value) => {
self.write_literal(w, graph, value)?;
}
ObjectNode::Collection(lst) => {
self.write_collection(w, graph, lst, flags)?;
}
ObjectNode::Statement(_) => {
unreachable!("RDF-* Statements are not supported in Turtle representation")
}
}
Ok(())
}
#[inline(always)]
fn write_collection<W: Write>(
&self,
w: &mut W,
graph: &Graph,
collection: &Collection,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_collection", ?collection, ?flags);
if !collection.is_empty() {
self.indent();
write!(w, "{COLLECTION_START}")?;
self.new_line(w, flags)?;
for (idx, object) in collection.iter().enumerate() {
self.write_object(w, graph, object, flags)?;
if idx < collection.len() - 1 {
write!(w, "{OBJECT_SEPARATOR}{SPACE_SEPARATOR}")?;
}
}
if self.options.outdent_collection_end {
self.outdent();
}
self.new_line(w, flags)?;
write!(w, "{COLLECTION_END}")?;
if !self.options.outdent_collection_end {
self.outdent();
}
} else {
write!(w, "{COLLECTION_START}{COLLECTION_END}")?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_predicate_object<W: Write>(
&self,
w: &mut W,
graph: &Graph,
group: PredicateGroupOrdering,
subject: &SubjectNode,
predicate: &Iri,
max_len: usize,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(
name: "write_predicate_object",
?subject, ?predicate, ?group, max_len, ?flags
);
self.write_verb(w, graph, group, predicate, max_len, flags)?;
let mut objects = graph
.objects_for(subject, predicate)
.into_iter()
.collect_vec();
let is_collection_of_objects = objects.len() > 1;
if is_collection_of_objects {
objects.sort_by_key(|o| self.object_sort_key(graph, o).unwrap_or_default());
}
let mut o_iter = objects.iter().peekable();
while let Some(object) = o_iter.next() {
let next_object = o_iter.peek();
let flags = WriterStatusFlags {
is_next_object_blank: next_object.is_some() && next_object.unwrap().is_blank(),
is_last_of_predicate: next_object.is_none(),
..flags
};
self.write_predicate_object_object(w, graph, object, max_len, flags)?;
}
Ok(())
}
fn write_blank_node_property_list<W: Write>(
&self,
w: &mut W,
graph: &Graph,
object: &ObjectNode,
flags: WriterStatusFlags,
) -> Result<()> {
trace!(name: "write_blank_node_property_list", ?object, ?flags);
self.indent();
write!(w, "{BLANK_NODE_START}")?;
let inner_subject = object.to_subject().unwrap();
self.write_predicate_object_list(w, graph, &inner_subject, flags)?;
self.new_line(w, flags)?;
write!(w, "{BLANK_NODE_END}")?;
Ok(())
}
}
static PREDICATE_GROUP_MAP: LazyLock<BTreeMap<Iri, PredicateGroupOrdering>> =
LazyLock::new(PredicateGroupOrdering::new_mapping);
impl PredicateGroupOrdering {
fn group_predicates<'a>(predicates: &[&'a Iri]) -> Vec<(PredicateGroupOrdering, Vec<&'a Iri>)> {
let mut result = predicates
.iter()
.chunk_by(Self::group_predicate)
.into_iter()
.map(|(triple_type, group)| (triple_type, group.cloned().collect()))
.collect::<Vec<(PredicateGroupOrdering, Vec<&Iri>)>>();
result.sort_by_key(|a| a.0);
result
}
fn group_predicate(predicate: &&&Iri) -> PredicateGroupOrdering {
PREDICATE_GROUP_MAP
.get(predicate)
.map(|v| *v)
.unwrap_or_else(|| PredicateGroupOrdering::Other)
}
fn new_mapping() -> BTreeMap<Iri, Self> {
vec![
(rdf::a_type().clone(), Self::Type),
(rdfs::subclass_of().clone(), Self::Type),
(rdfs::subproperty_of().clone(), Self::Type),
(owl::equivalent_class().clone(), Self::Type),
(rdfs::label().clone(), Self::Label),
(skos::pref_label().clone(), Self::Label),
(skos::alt_label().clone(), Self::Label),
(skos::hidden_label().clone(), Self::Label),
(foaf::name().clone(), Self::Label),
(elements::title().clone(), Self::Label),
(rdfs::comment().clone(), Self::Comment),
(elements::description().clone(), Self::Comment),
(skos::definition().clone(), Self::Comment),
(skos::note().clone(), Self::Comment),
(skos::scope_note().clone(), Self::Comment),
(skos::editorial_note().clone(), Self::Comment),
]
.into_iter()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::PredicateGroupOrdering::*;
use super::{PredicateGroupOrdering, TurtleWriter};
use objio::ObjectWriter;
use rdftk_core::model::graph::Graph;
use rdftk_core::model::literal::Literal;
use rdftk_core::model::statement::{BlankNode, Collection, ObjectNode, Statement};
use rdftk_names::rdfs;
use std::str::FromStr;
#[test]
fn test_order() {
let mut v: Vec<PredicateGroupOrdering> = vec![Comment, Label, Type, Other];
v.sort();
let sorted = format!("{:?}", v);
assert_eq!(sorted, "[Type, Label, Comment, Other]");
}
#[test]
fn test_blank_collection_object() {
let list: Vec<ObjectNode> = vec![
BlankNode::from_str("aa").unwrap().into(),
BlankNode::from_str("bb").unwrap().into(),
BlankNode::from_str("cc").unwrap().into(),
];
let collection: Collection = list.into();
let statement = Statement::new(BlankNode::generate(), rdfs::label().clone(), collection);
let graph = Graph::from(vec![statement]);
let writer = TurtleWriter::default();
writer.write(&mut std::io::stdout(), &graph).unwrap();
}
#[test]
fn test_literal_collection_object() {
let list: Vec<ObjectNode> = vec![
Literal::plain("aa").into(),
Literal::plain("bb").into(),
Literal::plain("cc").into(),
];
let collection: Collection = list.into();
let statement = Statement::new(BlankNode::generate(), rdfs::label().clone(), collection);
let graph = Graph::from(vec![statement]);
let writer = TurtleWriter::default();
writer.write(&mut std::io::stdout(), &graph).unwrap();
}
}