use crate::error::{TurtleParseError, TurtleResult};
use crate::toolkit::{FormattedWriter, SerializationConfig, Serializer};
#[cfg(feature = "rdf-12")]
#[allow(unused_imports)]
use oxirs_core::model::literal::BaseDirection;
use oxirs_core::model::{Object, Predicate, QuotedTriple, Subject, Triple};
use std::collections::HashMap;
use std::io::Write;
#[derive(Debug, Clone)]
pub struct TurtleSerializer {
config: SerializationConfig,
}
impl Default for TurtleSerializer {
fn default() -> Self {
Self::new()
}
}
impl TurtleSerializer {
pub fn new() -> Self {
Self {
config: SerializationConfig::default(),
}
}
pub fn with_config(config: SerializationConfig) -> Self {
Self { config }
}
pub fn with_auto_prefixes(triples: &[Triple]) -> Self {
let prefixes = Self::auto_generate_prefixes(triples);
let config = SerializationConfig::default().with_use_prefixes(true);
let mut config_with_prefixes = config;
config_with_prefixes.prefixes = prefixes;
Self {
config: config_with_prefixes,
}
}
pub fn auto_generate_prefixes(triples: &[Triple]) -> HashMap<String, String> {
let mut iri_counts: HashMap<String, usize> = HashMap::new();
for triple in triples {
if let Subject::NamedNode(nn) = triple.subject() {
if let Some(namespace) = Self::extract_namespace(nn.as_str()) {
*iri_counts.entry(namespace).or_insert(0) += 1;
}
}
if let Predicate::NamedNode(nn) = triple.predicate() {
if let Some(namespace) = Self::extract_namespace(nn.as_str()) {
*iri_counts.entry(namespace).or_insert(0) += 1;
}
}
if let Object::NamedNode(nn) = triple.object() {
if let Some(namespace) = Self::extract_namespace(nn.as_str()) {
*iri_counts.entry(namespace).or_insert(0) += 1;
}
}
}
let mut prefixes = HashMap::new();
let mut prefix_counter = 1;
for (namespace, count) in iri_counts {
if count > 1 {
let prefix = Self::suggest_prefix(&namespace, prefix_counter);
prefixes.insert(prefix, namespace);
prefix_counter += 1;
}
}
Self::add_well_known_prefixes(&mut prefixes, triples);
prefixes
}
fn extract_namespace(iri: &str) -> Option<String> {
let last_separator = iri.rfind(['#', '/'])?;
Some(iri[..=last_separator].to_string())
}
fn suggest_prefix(namespace: &str, counter: usize) -> String {
if namespace.contains("example.org") {
return "ex".to_string();
} else if namespace.contains("w3.org/1999/02/22-rdf-syntax-ns#") {
return "rdf".to_string();
} else if namespace.contains("w3.org/2000/01/rdf-schema#") {
return "rdfs".to_string();
} else if namespace.contains("w3.org/2002/07/owl#") {
return "owl".to_string();
} else if namespace.contains("xmlns.com/foaf") {
return "foaf".to_string();
} else if namespace.contains("purl.org/dc") {
return "dc".to_string();
} else if namespace.contains("schema.org") {
return "schema".to_string();
}
format!("ns{counter}")
}
fn add_well_known_prefixes(prefixes: &mut HashMap<String, String>, triples: &[Triple]) {
let well_known = [
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
("xsd", "http://www.w3.org/2001/XMLSchema#"),
("owl", "http://www.w3.org/2002/07/owl#"),
];
for (prefix, iri) in &well_known {
let used = triples.iter().any(|t| Self::triple_uses_namespace(t, iri));
if used && !prefixes.values().any(|v| v == iri) {
prefixes.insert(prefix.to_string(), iri.to_string());
}
}
}
fn triple_uses_namespace(triple: &Triple, namespace: &str) -> bool {
if let Subject::NamedNode(nn) = triple.subject() {
if nn.as_str().starts_with(namespace) {
return true;
}
}
if let Predicate::NamedNode(nn) = triple.predicate() {
if nn.as_str().starts_with(namespace) {
return true;
}
}
if let Object::NamedNode(nn) = triple.object() {
if nn.as_str().starts_with(namespace) {
return true;
}
}
false
}
}
impl Serializer<Triple> for TurtleSerializer {
fn serialize<W: Write>(&self, triples: &[Triple], writer: W) -> TurtleResult<()> {
let mut formatted_writer = FormattedWriter::new(writer, self.config.clone());
for (prefix, iri) in &self.config.prefixes {
formatted_writer
.write_str(&format!("@prefix {prefix}: <{iri}> ."))
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
if let Some(ref base) = self.config.base_iri {
formatted_writer
.write_str(&format!("@base <{base}> ."))
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
if !self.config.prefixes.is_empty() || self.config.base_iri.is_some() {
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
for triple in triples {
self.serialize_item_formatted(triple, &mut formatted_writer)?;
formatted_writer
.write_str(" .")
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
Ok(())
}
fn serialize_item<W: Write>(&self, triple: &Triple, writer: W) -> TurtleResult<()> {
let mut formatted_writer = FormattedWriter::new(writer, self.config.clone());
self.serialize_item_formatted(triple, &mut formatted_writer)
}
}
impl TurtleSerializer {
fn serialize_item_formatted<W: Write>(
&self,
triple: &Triple,
writer: &mut FormattedWriter<W>,
) -> TurtleResult<()> {
match triple.subject() {
Subject::NamedNode(nn) => {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
Subject::BlankNode(bn) => {
writer
.write_str(&format!("_:{}", bn.as_str()))
.map_err(TurtleParseError::io)?;
}
Subject::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
Subject::QuotedTriple(qt) => {
writer.write_str("<< ").map_err(TurtleParseError::io)?;
Self::serialize_quoted_triple(qt, writer)?;
writer.write_str(" >>").map_err(TurtleParseError::io)?;
}
}
writer.write_space().map_err(TurtleParseError::io)?;
match triple.predicate() {
Predicate::NamedNode(nn) => {
if nn.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
writer.write_str("a").map_err(TurtleParseError::io)?;
} else {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
}
Predicate::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
}
writer.write_space().map_err(TurtleParseError::io)?;
match triple.object() {
Object::NamedNode(nn) => {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
Object::BlankNode(bn) => {
writer
.write_str(&format!("_:{}", bn.as_str()))
.map_err(TurtleParseError::io)?;
}
Object::Literal(literal) => {
let escaped = writer.escape_string(literal.value());
writer.write_str(&escaped).map_err(TurtleParseError::io)?;
if let Some(language) = literal.language() {
#[cfg(feature = "rdf-12")]
if let Some(direction) = literal.direction() {
writer
.write_str(&format!("@{language}--{direction}"))
.map_err(TurtleParseError::io)?;
} else {
writer
.write_str(&format!("@{language}"))
.map_err(TurtleParseError::io)?;
}
#[cfg(not(feature = "rdf-12"))]
writer
.write_str(&format!("@{language}"))
.map_err(TurtleParseError::io)?;
} else if literal.datatype().as_str() != "http://www.w3.org/2001/XMLSchema#string" {
let datatype_abbrev = writer.abbreviate_iri(literal.datatype().as_str());
writer
.write_str(&format!("^^{datatype_abbrev}"))
.map_err(TurtleParseError::io)?;
}
}
Object::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
Object::QuotedTriple(qt) => {
writer.write_str("<< ").map_err(TurtleParseError::io)?;
Self::serialize_quoted_triple(qt, writer)?;
writer.write_str(" >>").map_err(TurtleParseError::io)?;
}
}
Ok(())
}
fn serialize_quoted_triple<W: Write>(
qt: &QuotedTriple,
writer: &mut FormattedWriter<W>,
) -> TurtleResult<()> {
match qt.subject() {
Subject::NamedNode(nn) => {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
Subject::BlankNode(bn) => {
writer
.write_str(&format!("_:{}", bn.as_str()))
.map_err(TurtleParseError::io)?;
}
Subject::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
Subject::QuotedTriple(inner_qt) => {
writer.write_str("<< ").map_err(TurtleParseError::io)?;
Self::serialize_quoted_triple(inner_qt, writer)?;
writer.write_str(" >>").map_err(TurtleParseError::io)?;
}
}
writer.write_space().map_err(TurtleParseError::io)?;
match qt.predicate() {
Predicate::NamedNode(nn) => {
if nn.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
writer.write_str("a").map_err(TurtleParseError::io)?;
} else {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
}
Predicate::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
}
writer.write_space().map_err(TurtleParseError::io)?;
match qt.object() {
Object::NamedNode(nn) => {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
Object::BlankNode(bn) => {
writer
.write_str(&format!("_:{}", bn.as_str()))
.map_err(TurtleParseError::io)?;
}
Object::Literal(literal) => {
let escaped = writer.escape_string(literal.value());
writer.write_str(&escaped).map_err(TurtleParseError::io)?;
if let Some(language) = literal.language() {
#[cfg(feature = "rdf-12")]
if let Some(direction) = literal.direction() {
writer
.write_str(&format!("@{language}--{direction}"))
.map_err(TurtleParseError::io)?;
} else {
writer
.write_str(&format!("@{language}"))
.map_err(TurtleParseError::io)?;
}
#[cfg(not(feature = "rdf-12"))]
writer
.write_str(&format!("@{language}"))
.map_err(TurtleParseError::io)?;
} else if literal.datatype().as_str() != "http://www.w3.org/2001/XMLSchema#string" {
let datatype_abbrev = writer.abbreviate_iri(literal.datatype().as_str());
writer
.write_str(&format!("^^{datatype_abbrev}"))
.map_err(TurtleParseError::io)?;
}
}
Object::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
Object::QuotedTriple(inner_qt) => {
writer.write_str("<< ").map_err(TurtleParseError::io)?;
Self::serialize_quoted_triple(inner_qt, writer)?;
writer.write_str(" >>").map_err(TurtleParseError::io)?;
}
}
Ok(())
}
pub fn serialize_optimized<W: Write>(&self, triples: &[Triple], writer: W) -> TurtleResult<()> {
let mut formatted_writer = FormattedWriter::new(writer, self.config.clone());
for (prefix, iri) in &self.config.prefixes {
formatted_writer
.write_str(&format!("@prefix {prefix}: <{iri}> ."))
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
if let Some(ref base) = self.config.base_iri {
formatted_writer
.write_str(&format!("@base <{base}> ."))
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
if !self.config.prefixes.is_empty() || self.config.base_iri.is_some() {
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
let grouped = self.group_triples_by_subject(triples);
for (idx, (subject, predicate_map)) in grouped.iter().enumerate() {
if idx > 0 {
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
self.serialize_subject_group(subject, predicate_map, &mut formatted_writer)?;
}
Ok(())
}
fn group_triples_by_subject(
&self,
triples: &[Triple],
) -> Vec<(Subject, HashMap<Predicate, Vec<Object>>)> {
use std::collections::HashMap;
let mut subject_map: HashMap<String, Vec<&Triple>> = HashMap::new();
for triple in triples {
let subject_key = format!("{:?}", triple.subject());
subject_map.entry(subject_key).or_default().push(triple);
}
let mut result = Vec::new();
for triples_group in subject_map.values() {
if triples_group.is_empty() {
continue;
}
let subject = triples_group[0].subject().clone();
let mut predicate_map: HashMap<String, Vec<Object>> = HashMap::new();
for triple in triples_group {
let predicate_key = format!("{:?}", triple.predicate());
predicate_map
.entry(predicate_key)
.or_default()
.push(triple.object().clone());
}
let mut typed_predicate_map = HashMap::new();
for triple in triples_group {
let predicate = triple.predicate().clone();
let predicate_key = format!("{:?}", &predicate);
if let Some(objects) = predicate_map.get(&predicate_key) {
typed_predicate_map.insert(predicate, objects.clone());
}
}
result.push((subject, typed_predicate_map));
}
result
}
fn serialize_subject_group<W: Write>(
&self,
subject: &Subject,
predicate_map: &HashMap<Predicate, Vec<Object>>,
writer: &mut FormattedWriter<W>,
) -> TurtleResult<()> {
self.serialize_subject(subject, writer)?;
writer.write_space().map_err(TurtleParseError::io)?;
let mut predicate_iter = predicate_map.iter().peekable();
while let Some((predicate, objects)) = predicate_iter.next() {
self.serialize_predicate(predicate, writer)?;
writer.write_space().map_err(TurtleParseError::io)?;
for (obj_idx, object) in objects.iter().enumerate() {
if obj_idx > 0 {
writer.write_str(", ").map_err(TurtleParseError::io)?;
}
self.serialize_object(object, writer)?;
}
if predicate_iter.peek().is_some() {
writer.write_str(" ;").map_err(TurtleParseError::io)?;
writer.write_newline().map_err(TurtleParseError::io)?;
if self.config.pretty {
writer
.write_str(" ")
.map_err(TurtleParseError::io)?;
}
} else {
writer.write_str(" .").map_err(TurtleParseError::io)?;
writer.write_newline().map_err(TurtleParseError::io)?;
}
}
Ok(())
}
fn serialize_subject<W: Write>(
&self,
subject: &Subject,
writer: &mut FormattedWriter<W>,
) -> TurtleResult<()> {
match subject {
Subject::NamedNode(nn) => {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
Subject::BlankNode(bn) => {
writer
.write_str(&format!("_:{}", bn.as_str()))
.map_err(TurtleParseError::io)?;
}
Subject::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
Subject::QuotedTriple(qt) => {
writer.write_str("<< ").map_err(TurtleParseError::io)?;
Self::serialize_quoted_triple(qt, writer)?;
writer.write_str(" >>").map_err(TurtleParseError::io)?;
}
}
Ok(())
}
fn serialize_predicate<W: Write>(
&self,
predicate: &Predicate,
writer: &mut FormattedWriter<W>,
) -> TurtleResult<()> {
match predicate {
Predicate::NamedNode(nn) => {
if nn.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
writer.write_str("a").map_err(TurtleParseError::io)?;
} else {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
}
Predicate::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
}
Ok(())
}
fn serialize_object<W: Write>(
&self,
object: &Object,
writer: &mut FormattedWriter<W>,
) -> TurtleResult<()> {
self.serialize_object_with_triples(object, writer, &[])
}
fn serialize_object_with_triples<W: Write>(
&self,
object: &Object,
writer: &mut FormattedWriter<W>,
all_triples: &[Triple],
) -> TurtleResult<()> {
match object {
Object::NamedNode(nn) => {
let abbrev = writer.abbreviate_iri(nn.as_str());
writer.write_str(&abbrev).map_err(TurtleParseError::io)?;
}
Object::BlankNode(bn) => {
if !all_triples.is_empty() && self.is_collection_head(bn, all_triples) {
if let Some(items) = self.extract_collection_items(bn, all_triples) {
self.serialize_collection(&items, writer, all_triples)?;
return Ok(());
}
}
if !all_triples.is_empty() {
let bn_triples = self.find_blank_node_properties(bn, all_triples);
if !bn_triples.is_empty() && self.is_blank_node_only_object(bn, all_triples) {
writer.write_str("[ ").map_err(TurtleParseError::io)?;
self.serialize_blank_node_properties(&bn_triples, writer, all_triples)?;
writer.write_str(" ]").map_err(TurtleParseError::io)?;
return Ok(());
}
}
writer
.write_str(&format!("_:{}", bn.as_str()))
.map_err(TurtleParseError::io)?;
}
Object::Literal(literal) => {
let escaped = writer.escape_string(literal.value());
writer.write_str(&escaped).map_err(TurtleParseError::io)?;
if let Some(language) = literal.language() {
#[cfg(feature = "rdf-12")]
if let Some(direction) = literal.direction() {
writer
.write_str(&format!("@{language}--{direction}"))
.map_err(TurtleParseError::io)?;
} else {
writer
.write_str(&format!("@{language}"))
.map_err(TurtleParseError::io)?;
}
#[cfg(not(feature = "rdf-12"))]
writer
.write_str(&format!("@{language}"))
.map_err(TurtleParseError::io)?;
} else if literal.datatype().as_str() != "http://www.w3.org/2001/XMLSchema#string" {
let datatype_abbrev = writer.abbreviate_iri(literal.datatype().as_str());
writer
.write_str(&format!("^^{datatype_abbrev}"))
.map_err(TurtleParseError::io)?;
}
}
Object::Variable(var) => {
writer
.write_str(&format!("?{}", var.as_str()))
.map_err(TurtleParseError::io)?;
}
Object::QuotedTriple(qt) => {
writer.write_str("<< ").map_err(TurtleParseError::io)?;
Self::serialize_quoted_triple(qt, writer)?;
writer.write_str(" >>").map_err(TurtleParseError::io)?;
}
}
Ok(())
}
fn find_blank_node_properties<'a>(
&self,
bn: &oxirs_core::model::BlankNode,
triples: &'a [Triple],
) -> Vec<&'a Triple> {
triples
.iter()
.filter(|t| matches!(t.subject(), Subject::BlankNode(b) if b.as_str() == bn.as_str()))
.collect()
}
fn is_blank_node_only_object(
&self,
bn: &oxirs_core::model::BlankNode,
triples: &[Triple],
) -> bool {
let as_subject_count = triples
.iter()
.filter(|t| matches!(t.subject(), Subject::BlankNode(b) if b.as_str() == bn.as_str()))
.count();
let as_object_count = triples
.iter()
.filter(|t| matches!(t.object(), Object::BlankNode(b) if b.as_str() == bn.as_str()))
.count();
as_subject_count > 0 && as_object_count == 1
}
fn serialize_blank_node_properties<W: Write>(
&self,
triples: &[&Triple],
writer: &mut FormattedWriter<W>,
all_triples: &[Triple],
) -> TurtleResult<()> {
for (idx, triple) in triples.iter().enumerate() {
if idx > 0 {
writer.write_str(" ; ").map_err(TurtleParseError::io)?;
}
self.serialize_predicate(triple.predicate(), writer)?;
writer.write_space().map_err(TurtleParseError::io)?;
self.serialize_object_with_triples(triple.object(), writer, all_triples)?;
}
Ok(())
}
pub fn serialize_with_blank_node_optimization<W: Write>(
&self,
triples: &[Triple],
writer: W,
) -> TurtleResult<()> {
let mut formatted_writer = FormattedWriter::new(writer, self.config.clone());
for (prefix, iri) in &self.config.prefixes {
formatted_writer
.write_str(&format!("@prefix {prefix}: <{iri}> ."))
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
if let Some(ref base) = self.config.base_iri {
formatted_writer
.write_str(&format!("@base <{base}> ."))
.map_err(TurtleParseError::io)?;
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
if !self.config.prefixes.is_empty() || self.config.base_iri.is_some() {
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
let mut inlineable_bns = std::collections::HashSet::new();
for triple in triples {
if let Object::BlankNode(bn) = triple.object() {
if self.is_blank_node_only_object(bn, triples) {
inlineable_bns.insert(bn.as_str());
}
}
}
let main_triples: Vec<&Triple> = triples
.iter()
.filter(|t| {
if let Subject::BlankNode(bn) = t.subject() {
!inlineable_bns.contains(bn.as_str())
} else {
true
}
})
.collect();
let grouped = self.group_triples_by_subject_refs(&main_triples);
for (idx, (subject, predicate_map)) in grouped.iter().enumerate() {
if idx > 0 {
formatted_writer
.write_newline()
.map_err(TurtleParseError::io)?;
}
self.serialize_subject_group_with_blanks(
subject,
predicate_map,
&mut formatted_writer,
triples,
)?;
}
Ok(())
}
fn group_triples_by_subject_refs(
&self,
triples: &[&Triple],
) -> Vec<(Subject, HashMap<Predicate, Vec<Object>>)> {
let mut subject_map: HashMap<String, Vec<&Triple>> = HashMap::new();
for triple in triples {
let subject_key = format!("{:?}", triple.subject());
subject_map.entry(subject_key).or_default().push(triple);
}
let mut result = Vec::new();
for triples_group in subject_map.values() {
if triples_group.is_empty() {
continue;
}
let subject = triples_group[0].subject().clone();
let mut predicate_map: HashMap<String, Vec<Object>> = HashMap::new();
for triple in triples_group {
let predicate_key = format!("{:?}", triple.predicate());
predicate_map
.entry(predicate_key)
.or_default()
.push(triple.object().clone());
}
let mut typed_predicate_map = HashMap::new();
for triple in triples_group {
let predicate = triple.predicate().clone();
let predicate_key = format!("{:?}", &predicate);
if let Some(objects) = predicate_map.get(&predicate_key) {
typed_predicate_map.insert(predicate, objects.clone());
}
}
result.push((subject, typed_predicate_map));
}
result
}
fn serialize_subject_group_with_blanks<W: Write>(
&self,
subject: &Subject,
predicate_map: &HashMap<Predicate, Vec<Object>>,
writer: &mut FormattedWriter<W>,
all_triples: &[Triple],
) -> TurtleResult<()> {
self.serialize_subject(subject, writer)?;
writer.write_space().map_err(TurtleParseError::io)?;
let mut predicate_iter = predicate_map.iter().peekable();
while let Some((predicate, objects)) = predicate_iter.next() {
self.serialize_predicate(predicate, writer)?;
writer.write_space().map_err(TurtleParseError::io)?;
for (obj_idx, object) in objects.iter().enumerate() {
if obj_idx > 0 {
writer.write_str(", ").map_err(TurtleParseError::io)?;
}
self.serialize_object_with_triples(object, writer, all_triples)?;
}
if predicate_iter.peek().is_some() {
writer.write_str(" ;").map_err(TurtleParseError::io)?;
writer.write_newline().map_err(TurtleParseError::io)?;
if self.config.pretty {
writer
.write_str(" ")
.map_err(TurtleParseError::io)?;
}
} else {
writer.write_str(" .").map_err(TurtleParseError::io)?;
writer.write_newline().map_err(TurtleParseError::io)?;
}
}
Ok(())
}
fn is_collection_head(&self, bn: &oxirs_core::model::BlankNode, triples: &[Triple]) -> bool {
let rdf_first = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first";
let rdf_rest = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest";
let mut has_first = false;
let mut has_rest = false;
for triple in triples {
if let Subject::BlankNode(b) = triple.subject() {
if b.as_str() == bn.as_str() {
if let Predicate::NamedNode(nn) = triple.predicate() {
if nn.as_str() == rdf_first {
has_first = true;
} else if nn.as_str() == rdf_rest {
has_rest = true;
}
}
}
}
}
has_first && has_rest
}
fn extract_collection_items(
&self,
bn: &oxirs_core::model::BlankNode,
triples: &[Triple],
) -> Option<Vec<Object>> {
use std::collections::HashSet;
let rdf_first = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first";
let rdf_rest = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest";
let rdf_nil = "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil";
let mut items = Vec::new();
let mut current_bn = bn.clone();
let mut visited = HashSet::new();
loop {
if !visited.insert(current_bn.as_str().to_string()) {
return None; }
if items.len() > 1000 {
return None;
}
let mut first_item = None;
let mut rest_node = None;
for triple in triples {
if let Subject::BlankNode(b) = triple.subject() {
if b.as_str() == current_bn.as_str() {
if let Predicate::NamedNode(nn) = triple.predicate() {
if nn.as_str() == rdf_first {
first_item = Some(triple.object().clone());
} else if nn.as_str() == rdf_rest {
rest_node = Some(triple.object().clone());
}
}
}
}
}
let first = first_item?;
let rest = rest_node?;
items.push(first);
match &rest {
Object::NamedNode(nn) if nn.as_str() == rdf_nil => {
break;
}
Object::BlankNode(next_bn) => {
current_bn = next_bn.clone();
}
_ => {
return None;
}
}
}
Some(items)
}
fn serialize_collection<W: Write>(
&self,
items: &[Object],
writer: &mut FormattedWriter<W>,
all_triples: &[Triple],
) -> TurtleResult<()> {
writer.write_str("(").map_err(TurtleParseError::io)?;
for (idx, item) in items.iter().enumerate() {
if idx > 0 {
writer.write_space().map_err(TurtleParseError::io)?;
}
self.serialize_object_with_triples(item, writer, all_triples)?;
}
writer.write_str(")").map_err(TurtleParseError::io)?;
Ok(())
}
}