use crate::{
model::{dataset::Dataset, graph::Graph, GraphName, Quad},
parser::RdfFormat,
Result,
};
pub struct Serializer {
format: RdfFormat,
}
impl Serializer {
pub fn new(format: RdfFormat) -> Self {
Serializer { format }
}
pub fn serialize_graph(&self, graph: &Graph) -> Result<String> {
match self.format {
RdfFormat::Turtle => self.serialize_turtle(graph),
RdfFormat::NTriples => self.serialize_ntriples(graph),
RdfFormat::TriG => self.serialize_trig_graph(graph),
RdfFormat::NQuads => self.serialize_nquads_graph(graph),
RdfFormat::RdfXml => self.serialize_rdfxml(graph),
RdfFormat::JsonLd => self.serialize_jsonld(graph),
}
}
pub fn serialize_dataset(&self, dataset: &Dataset) -> Result<String> {
match self.format {
RdfFormat::Turtle => Err(crate::OxirsError::Serialize(
"Turtle format does not support datasets (use TriG instead)".to_string(),
)),
RdfFormat::NTriples => Err(crate::OxirsError::Serialize(
"N-Triples format does not support datasets (use N-Quads instead)".to_string(),
)),
RdfFormat::TriG => self.serialize_trig_dataset(dataset),
RdfFormat::NQuads => self.serialize_nquads_dataset(dataset),
RdfFormat::RdfXml => Err(crate::OxirsError::Serialize(
"RDF/XML dataset serialization not yet implemented".to_string(),
)),
RdfFormat::JsonLd => Err(crate::OxirsError::Serialize(
"JSON-LD dataset serialization not yet implemented".to_string(),
)),
}
}
pub fn serialize(&self, graph: &Graph) -> Result<String> {
self.serialize_graph(graph)
}
fn serialize_turtle(&self, graph: &Graph) -> Result<String> {
let mut serializer = TurtleSerializer::new();
serializer.serialize_graph(graph)
}
fn serialize_ntriples(&self, graph: &Graph) -> Result<String> {
let mut result = String::new();
for triple in graph.iter() {
match triple.subject() {
crate::model::Subject::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
crate::model::Subject::BlankNode(node) => {
result.push_str(&format!("{node}"));
}
crate::model::Subject::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Triples serialization".to_string(),
));
}
crate::model::Subject::QuotedTriple(_) => {
return Err(crate::OxirsError::Serialize(
"Quoted triples not supported in N-Triples serialization".to_string(),
));
}
}
result.push(' ');
match triple.predicate() {
crate::model::Predicate::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
crate::model::Predicate::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Triples serialization".to_string(),
));
}
}
result.push(' ');
match triple.object() {
crate::model::Object::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
crate::model::Object::BlankNode(node) => {
result.push_str(&format!("{node}"));
}
crate::model::Object::Literal(literal) => {
result.push('"');
for c in literal.value().chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
_ => result.push(c),
}
}
result.push('"');
if let Some(lang) = literal.language() {
result.push_str(&format!("@{lang}"));
} else {
let datatype = literal.datatype();
if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
result.push_str(&format!("^^<{}>", datatype.as_str()));
}
}
}
crate::model::Object::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Triples serialization".to_string(),
));
}
crate::model::Object::QuotedTriple(_) => {
return Err(crate::OxirsError::Serialize(
"Quoted triples not supported in N-Triples serialization".to_string(),
));
}
}
result.push_str(" .\n");
}
Ok(result)
}
fn serialize_rdfxml(&self, graph: &Graph) -> Result<String> {
use oxrdfxml::RdfXmlSerializer;
let mut output = Vec::new();
let mut serializer = RdfXmlSerializer::new().for_writer(&mut output);
for triple in graph.iter() {
let oxrdf_triple = self.convert_triple_to_oxrdf(triple)?;
serializer.serialize_triple(&oxrdf_triple).map_err(|e| {
crate::OxirsError::Serialize(format!("RDF/XML serialization error: {e}"))
})?;
}
serializer
.finish()
.map_err(|e| crate::OxirsError::Serialize(format!("RDF/XML finish error: {e}")))?;
String::from_utf8(output)
.map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
}
fn serialize_trig_graph(&self, graph: &Graph) -> Result<String> {
use oxttl::TriGSerializer;
let mut output = Vec::new();
let mut serializer = TriGSerializer::new().for_writer(&mut output);
for triple in graph.iter() {
let oxrdf_quad = self.convert_triple_to_oxrdf_quad(triple, None)?;
serializer.serialize_quad(&oxrdf_quad).map_err(|e| {
crate::OxirsError::Serialize(format!("TriG serialization error: {e}"))
})?;
}
serializer
.finish()
.map_err(|e| crate::OxirsError::Serialize(format!("TriG finish error: {e}")))?;
String::from_utf8(output)
.map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
}
fn serialize_trig_dataset(&self, dataset: &Dataset) -> Result<String> {
use oxttl::TriGSerializer;
let mut output = Vec::new();
let mut serializer = TriGSerializer::new().for_writer(&mut output);
for quad in dataset.iter() {
let oxrdf_quad = self.convert_quad_to_oxrdf(&quad)?;
serializer.serialize_quad(&oxrdf_quad).map_err(|e| {
crate::OxirsError::Serialize(format!("TriG serialization error: {e}"))
})?;
}
serializer
.finish()
.map_err(|e| crate::OxirsError::Serialize(format!("TriG finish error: {e}")))?;
String::from_utf8(output)
.map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
}
fn serialize_nquads_graph(&self, graph: &Graph) -> Result<String> {
let mut result = String::new();
for triple in graph.iter() {
result.push_str(&self.serialize_quad_to_nquads(&Quad::from_triple(triple.clone()))?);
}
Ok(result)
}
fn serialize_nquads_dataset(&self, dataset: &Dataset) -> Result<String> {
let mut result = String::new();
for quad in dataset.iter() {
result.push_str(&self.serialize_quad_to_nquads(&quad)?);
}
Ok(result)
}
pub fn serialize_quad_to_nquads(&self, quad: &Quad) -> Result<String> {
let mut result = String::new();
match quad.subject() {
crate::model::Subject::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
crate::model::Subject::BlankNode(node) => {
result.push_str(&format!("{node}"));
}
crate::model::Subject::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Quads serialization".to_string(),
));
}
crate::model::Subject::QuotedTriple(_) => {
return Err(crate::OxirsError::Serialize(
"Quoted triples not supported in N-Quads serialization".to_string(),
));
}
}
result.push(' ');
match quad.predicate() {
crate::model::Predicate::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
crate::model::Predicate::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Quads serialization".to_string(),
));
}
}
result.push(' ');
match quad.object() {
crate::model::Object::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
crate::model::Object::BlankNode(node) => {
result.push_str(&format!("{node}"));
}
crate::model::Object::Literal(literal) => {
result.push('"');
for c in literal.value().chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
_ => result.push(c),
}
}
result.push('"');
if let Some(lang) = literal.language() {
result.push_str(&format!("@{lang}"));
} else {
let datatype = literal.datatype();
if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
result.push_str(&format!("^^<{}>", datatype.as_str()));
}
}
}
crate::model::Object::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Quads serialization".to_string(),
));
}
crate::model::Object::QuotedTriple(_) => {
return Err(crate::OxirsError::Serialize(
"Quoted triples not supported in N-Quads serialization".to_string(),
));
}
}
result.push(' ');
match quad.graph_name() {
GraphName::NamedNode(node) => {
result.push_str(&format!("<{}>", node.as_str()));
}
GraphName::BlankNode(node) => {
result.push_str(&format!("{node}"));
}
GraphName::Variable(_) => {
return Err(crate::OxirsError::Serialize(
"Variables not supported in N-Quads serialization".to_string(),
));
}
GraphName::DefaultGraph => {
result.pop(); result.push_str(" .\n");
return Ok(result);
}
}
result.push_str(" .\n");
Ok(result)
}
fn serialize_jsonld(&self, graph: &Graph) -> Result<String> {
use oxjsonld::JsonLdSerializer;
let mut output = Vec::new();
let mut serializer = JsonLdSerializer::new().for_writer(&mut output);
for triple in graph.iter() {
let oxrdf_quad = self.convert_triple_to_oxrdf_quad(triple, None)?;
serializer.serialize_quad(&oxrdf_quad).map_err(|e| {
crate::OxirsError::Serialize(format!("JSON-LD serialization error: {e}"))
})?;
}
serializer
.finish()
.map_err(|e| crate::OxirsError::Serialize(format!("JSON-LD finish error: {e}")))?;
String::from_utf8(output)
.map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
}
fn convert_triple_to_oxrdf(&self, triple: &crate::model::Triple) -> Result<oxrdf::Triple> {
let subject = match triple.subject() {
crate::model::Subject::NamedNode(n) => {
oxrdf::NamedOrBlankNode::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
}
crate::model::Subject::BlankNode(b) => {
oxrdf::NamedOrBlankNode::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
}
_ => {
return Err(crate::OxirsError::Serialize(
"Variables and quoted triples not supported in serialization".to_string(),
))
}
};
let predicate = match triple.predicate() {
crate::model::Predicate::NamedNode(n) => oxrdf::NamedNode::new_unchecked(n.as_str()),
_ => {
return Err(crate::OxirsError::Serialize(
"Variable predicates not supported in serialization".to_string(),
))
}
};
let object = match triple.object() {
crate::model::Object::NamedNode(n) => {
oxrdf::Term::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
}
crate::model::Object::BlankNode(b) => {
oxrdf::Term::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
}
crate::model::Object::Literal(l) => {
let literal = if let Some(lang) = l.language() {
oxrdf::Literal::new_language_tagged_literal_unchecked(l.value(), lang)
} else {
let datatype = oxrdf::NamedNode::new_unchecked(l.datatype().as_str());
oxrdf::Literal::new_typed_literal(l.value(), datatype)
};
oxrdf::Term::Literal(literal)
}
_ => {
return Err(crate::OxirsError::Serialize(
"Variable objects and quoted triples not supported in serialization"
.to_string(),
))
}
};
Ok(oxrdf::Triple::new(subject, predicate, object))
}
fn convert_triple_to_oxrdf_quad(
&self,
triple: &crate::model::Triple,
graph_name: Option<&oxrdf::GraphName>,
) -> Result<oxrdf::Quad> {
let oxrdf_triple = self.convert_triple_to_oxrdf(triple)?;
let graph = graph_name
.cloned()
.unwrap_or(oxrdf::GraphName::DefaultGraph);
Ok(oxrdf::Quad::new(
oxrdf_triple.subject,
oxrdf_triple.predicate,
oxrdf_triple.object,
graph,
))
}
fn convert_quad_to_oxrdf(&self, quad: &Quad) -> Result<oxrdf::Quad> {
let subject = match quad.subject() {
crate::model::Subject::NamedNode(n) => {
oxrdf::NamedOrBlankNode::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
}
crate::model::Subject::BlankNode(b) => {
oxrdf::NamedOrBlankNode::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
}
_ => {
return Err(crate::OxirsError::Serialize(
"Variables and quoted triples not supported in serialization".to_string(),
))
}
};
let predicate = match quad.predicate() {
crate::model::Predicate::NamedNode(n) => oxrdf::NamedNode::new_unchecked(n.as_str()),
_ => {
return Err(crate::OxirsError::Serialize(
"Variable predicates not supported in serialization".to_string(),
))
}
};
let object = match quad.object() {
crate::model::Object::NamedNode(n) => {
oxrdf::Term::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
}
crate::model::Object::BlankNode(b) => {
oxrdf::Term::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
}
crate::model::Object::Literal(l) => {
let literal = if let Some(lang) = l.language() {
oxrdf::Literal::new_language_tagged_literal_unchecked(l.value(), lang)
} else {
let datatype = oxrdf::NamedNode::new_unchecked(l.datatype().as_str());
oxrdf::Literal::new_typed_literal(l.value(), datatype)
};
oxrdf::Term::Literal(literal)
}
_ => {
return Err(crate::OxirsError::Serialize(
"Variable objects and quoted triples not supported in serialization"
.to_string(),
))
}
};
let graph_name = match quad.graph_name() {
GraphName::NamedNode(n) => {
oxrdf::GraphName::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
}
GraphName::BlankNode(b) => {
oxrdf::GraphName::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
}
GraphName::DefaultGraph => oxrdf::GraphName::DefaultGraph,
_ => {
return Err(crate::OxirsError::Serialize(
"Variable graph names not supported in serialization".to_string(),
))
}
};
Ok(oxrdf::Quad::new(subject, predicate, object, graph_name))
}
}
struct TurtleSerializer {
prefixes: std::collections::HashMap<String, String>,
used_namespaces: std::collections::HashSet<String>,
}
impl TurtleSerializer {
fn new() -> Self {
let mut prefixes = std::collections::HashMap::new();
prefixes.insert(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
"rdf".to_string(),
);
prefixes.insert(
"http://www.w3.org/2000/01/rdf-schema#".to_string(),
"rdfs".to_string(),
);
prefixes.insert(
"http://www.w3.org/2001/XMLSchema#".to_string(),
"xsd".to_string(),
);
prefixes.insert("http://xmlns.com/foaf/0.1/".to_string(), "foaf".to_string());
prefixes.insert(
"http://purl.org/dc/elements/1.1/".to_string(),
"dc".to_string(),
);
TurtleSerializer {
prefixes,
used_namespaces: std::collections::HashSet::new(),
}
}
fn serialize_graph(&mut self, graph: &Graph) -> Result<String> {
self.collect_namespaces(graph);
let mut result = String::new();
let mut prefix_entries: Vec<_> = self.prefixes.iter().collect();
prefix_entries.sort_by_key(|(_, prefix)| *prefix);
for (namespace, prefix) in prefix_entries {
if self.used_namespaces.contains(namespace) {
result.push_str(&format!("@prefix {prefix}: <{namespace}> .\n"));
}
}
if !self.prefixes.is_empty() && !graph.is_empty() {
result.push('\n');
}
let mut subjects_map: std::collections::HashMap<
crate::model::Subject,
Vec<&crate::model::Triple>,
> = std::collections::HashMap::new();
for triple in graph.iter() {
subjects_map
.entry(triple.subject().clone())
.or_default()
.push(triple);
}
let mut subject_entries: Vec<_> = subjects_map.iter().collect();
subject_entries.sort_by_key(|(subject, _)| format!("{subject}"));
for (i, (subject, triples)) in subject_entries.iter().enumerate() {
if i > 0 {
result.push('\n');
}
result.push_str(&self.serialize_subject(subject)?);
let mut predicates_map: std::collections::HashMap<
crate::model::Predicate,
Vec<&crate::model::Object>,
> = std::collections::HashMap::new();
for triple in triples.iter() {
predicates_map
.entry(triple.predicate().clone())
.or_default()
.push(triple.object());
}
let mut predicate_entries: Vec<_> = predicates_map.iter().collect();
predicate_entries.sort_by_key(|(predicate, _)| format!("{predicate}"));
for (j, (predicate, objects)) in predicate_entries.iter().enumerate() {
if j == 0 {
result.push(' ');
} else {
result.push_str(" ;\n ");
}
result.push_str(&self.serialize_predicate(predicate)?);
result.push(' ');
for (k, object) in objects.iter().enumerate() {
if k > 0 {
result.push_str(", ");
}
result.push_str(&self.serialize_object(object)?);
}
}
result.push_str(" .\n");
}
Ok(result)
}
fn collect_namespaces(&mut self, graph: &Graph) {
for triple in graph.iter() {
self.mark_namespace_used(triple.subject());
self.mark_namespace_used_predicate(triple.predicate());
self.mark_namespace_used_object(triple.object());
}
}
fn mark_namespace_used(&mut self, subject: &crate::model::Subject) {
if let crate::model::Subject::NamedNode(node) = subject {
if let Some(namespace) = self.extract_namespace(node.as_str()) {
self.used_namespaces.insert(namespace);
}
}
}
fn mark_namespace_used_predicate(&mut self, predicate: &crate::model::Predicate) {
if let crate::model::Predicate::NamedNode(node) = predicate {
if let Some(namespace) = self.extract_namespace(node.as_str()) {
self.used_namespaces.insert(namespace);
}
}
}
fn mark_namespace_used_object(&mut self, object: &crate::model::Object) {
match object {
crate::model::Object::NamedNode(node) => {
if let Some(namespace) = self.extract_namespace(node.as_str()) {
self.used_namespaces.insert(namespace);
}
}
crate::model::Object::Literal(literal) => {
let datatype = literal.datatype();
if let Some(namespace) = self.extract_namespace(datatype.as_str()) {
self.used_namespaces.insert(namespace);
}
}
_ => {}
}
}
fn extract_namespace(&self, iri: &str) -> Option<String> {
if let Some(hash_pos) = iri.rfind('#') {
Some(format!("{}#", &iri[..hash_pos]))
} else {
iri.rfind('/')
.map(|slash_pos| format!("{}/", &iri[..slash_pos]))
}
}
fn serialize_subject(&self, subject: &crate::model::Subject) -> Result<String> {
match subject {
crate::model::Subject::NamedNode(node) => self.serialize_iri(node.as_str()),
crate::model::Subject::BlankNode(node) => Ok(node.as_str().to_string()),
crate::model::Subject::Variable(_var) => Err(crate::OxirsError::Serialize(
"Variables not supported in Turtle serialization".to_string(),
)),
crate::model::Subject::QuotedTriple(qt) => Ok(format!(
"<< {} {} {} >>",
self.serialize_subject(qt.subject())?,
self.serialize_predicate(qt.predicate())?,
self.serialize_object(qt.object())?
)),
}
}
fn serialize_predicate(&self, predicate: &crate::model::Predicate) -> Result<String> {
match predicate {
crate::model::Predicate::NamedNode(node) => {
if node.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
Ok("a".to_string())
} else {
self.serialize_iri(node.as_str())
}
}
crate::model::Predicate::Variable(_) => Err(crate::OxirsError::Serialize(
"Variables not supported in Turtle serialization".to_string(),
)),
}
}
fn serialize_object(&self, object: &crate::model::Object) -> Result<String> {
match object {
crate::model::Object::NamedNode(node) => self.serialize_iri(node.as_str()),
crate::model::Object::BlankNode(node) => Ok(node.as_str().to_string()),
crate::model::Object::Literal(literal) => self.serialize_literal(literal),
crate::model::Object::Variable(_) => Err(crate::OxirsError::Serialize(
"Variables not supported in Turtle serialization".to_string(),
)),
crate::model::Object::QuotedTriple(qt) => Ok(format!(
"<< {} {} {} >>",
self.serialize_subject(qt.subject())?,
self.serialize_predicate(qt.predicate())?,
self.serialize_object(qt.object())?
)),
}
}
fn serialize_iri(&self, iri: &str) -> Result<String> {
for (namespace, prefix) in &self.prefixes {
if iri.starts_with(namespace) && self.used_namespaces.contains(namespace) {
let local_name = &iri[namespace.len()..];
if self.is_valid_local_name(local_name) {
return Ok(format!("{prefix}:{local_name}"));
}
}
}
Ok(format!("<{iri}>"))
}
fn is_valid_local_name(&self, name: &str) -> bool {
if name.is_empty() {
return true; }
let first_char = name
.chars()
.next()
.expect("non-empty name should have first char");
if !first_char.is_alphabetic() && first_char != '_' {
return false;
}
name.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
}
fn serialize_literal(&self, literal: &crate::model::Literal) -> Result<String> {
let mut result = String::new();
result.push('"');
for c in literal.value().chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
_ => result.push(c),
}
}
result.push('"');
if let Some(lang) = literal.language() {
result.push_str(&format!("@{lang}"));
} else {
let datatype = literal.datatype();
if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
result.push_str("^^");
result.push_str(&self.serialize_iri(datatype.as_str())?);
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::graph::Graph;
use crate::model::*;
fn create_test_graph() -> Graph {
let mut graph = Graph::new();
let subject = NamedNode::new("http://example.org/alice").expect("valid IRI");
let predicate = NamedNode::new("http://xmlns.com/foaf/0.1/name").expect("valid IRI");
let object = Literal::new("Alice Smith");
let triple1 = Triple::new(subject.clone(), predicate, object);
let age_pred = NamedNode::new("http://xmlns.com/foaf/0.1/age").expect("valid IRI");
let age_obj = Literal::new_typed("30", crate::vocab::xsd::INTEGER.clone());
let triple2 = Triple::new(subject.clone(), age_pred, age_obj);
let desc_pred = NamedNode::new("http://example.org/description").expect("valid IRI");
let desc_obj =
Literal::new_lang("Une personne", "fr").expect("construction should succeed");
let triple3 = Triple::new(subject, desc_pred, desc_obj);
let blank_subject = BlankNode::new("person1").expect("valid blank node id");
let knows_pred = NamedNode::new("http://xmlns.com/foaf/0.1/knows").expect("valid IRI");
let knows_obj = NamedNode::new("http://example.org/bob").expect("valid IRI");
let triple4 = Triple::new(blank_subject, knows_pred, knows_obj);
graph.insert(triple1);
graph.insert(triple2);
graph.insert(triple3);
graph.insert(triple4);
graph
}
#[test]
fn test_ntriples_serialization() {
let graph = create_test_graph();
let serializer = Serializer::new(RdfFormat::NTriples);
let result = serializer.serialize_graph(&graph);
assert!(result.is_ok());
let ntriples = result.expect("should have value");
assert!(!ntriples.is_empty());
for line in ntriples.lines() {
if !line.trim().is_empty() {
assert!(line.ends_with(" ."), "Line should end with ' .': {line}");
}
}
assert!(ntriples.contains("http://example.org/alice"));
assert!(ntriples.contains("http://xmlns.com/foaf/0.1/name"));
assert!(ntriples.contains("\"Alice Smith\""));
assert!(ntriples.contains("\"30\"^^<http://www.w3.org/2001/XMLSchema#integer>"));
assert!(ntriples.contains("\"Une personne\"@fr"));
assert!(ntriples.contains("_:person1"));
}
#[test]
fn test_literal_escaping() {
let mut graph = Graph::new();
let subject = NamedNode::new("http://example.org/test").expect("valid IRI");
let predicate = NamedNode::new("http://example.org/description").expect("valid IRI");
let object = Literal::new("Text with \"quotes\" and \n newlines \t and tabs");
let triple = Triple::new(subject, predicate, object);
graph.insert(triple);
let serializer = Serializer::new(RdfFormat::NTriples);
let result = serializer
.serialize_graph(&graph)
.expect("operation should succeed");
assert!(result.contains("\\\"quotes\\\""));
assert!(result.contains("\\n"));
assert!(result.contains("\\t"));
}
#[test]
fn test_empty_graph_serialization() {
let graph = Graph::new();
let serializer = Serializer::new(RdfFormat::NTriples);
let result = serializer.serialize_graph(&graph);
assert!(result.is_ok());
let ntriples = result.expect("should have value");
assert!(ntriples.is_empty());
}
#[test]
fn test_variable_serialization_error() {
let mut graph = Graph::new();
let variable_subject = Variable::new("x").expect("valid variable name");
let predicate = NamedNode::new("http://example.org/predicate").expect("valid IRI");
let object = Literal::new("test");
let triple = Triple::new(variable_subject, predicate, object);
graph.insert(triple);
let serializer = Serializer::new(RdfFormat::NTriples);
let result = serializer.serialize_graph(&graph);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Variables not supported"));
}
#[test]
fn test_turtle_serialization() {
let graph = create_test_graph();
let serializer = Serializer::new(RdfFormat::Turtle);
let result = serializer.serialize_graph(&graph);
assert!(result.is_ok());
let turtle = result.expect("should have value");
assert!(!turtle.is_empty());
assert!(turtle.contains("@prefix"));
assert!(turtle.ends_with(" .\n") || turtle.ends_with("."));
}
#[test]
fn test_turtle_serialization_with_prefixes() {
let mut graph = Graph::new();
let alice = NamedNode::new("http://example.org/alice").expect("valid IRI");
let name_pred = NamedNode::new("http://xmlns.com/foaf/0.1/name").expect("valid IRI");
let person_type = NamedNode::new("http://xmlns.com/foaf/0.1/Person").expect("valid IRI");
let rdf_type =
NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").expect("valid IRI");
let name_literal = Literal::new("Alice");
graph.insert(Triple::new(alice.clone(), name_pred, name_literal));
graph.insert(Triple::new(alice, rdf_type, person_type));
let serializer = Serializer::new(RdfFormat::Turtle);
let turtle = serializer
.serialize_graph(&graph)
.expect("operation should succeed");
assert!(turtle.contains("@prefix foaf: <http://xmlns.com/foaf/0.1/>"));
assert!(turtle.contains(" a "));
assert!(turtle.contains("foaf:"));
}
#[test]
fn test_turtle_serialization_abbreviated_syntax() {
let mut graph = Graph::new();
let alice = NamedNode::new("http://example.org/alice").expect("valid IRI");
let name_pred = NamedNode::new("http://xmlns.com/foaf/0.1/name").expect("valid IRI");
let age_pred = NamedNode::new("http://xmlns.com/foaf/0.1/age").expect("valid IRI");
let name_literal = Literal::new("Alice");
let age_literal = Literal::new_typed("30", crate::vocab::xsd::INTEGER.clone());
graph.insert(Triple::new(alice.clone(), name_pred, name_literal));
graph.insert(Triple::new(alice, age_pred, age_literal));
let serializer = Serializer::new(RdfFormat::Turtle);
let turtle = serializer
.serialize_graph(&graph)
.expect("operation should succeed");
assert!(turtle.contains(";"));
assert!(turtle.lines().count() >= 3); }
#[test]
fn test_turtle_serialization_literals() {
let mut graph = Graph::new();
let subject = NamedNode::new("http://example.org/test").expect("valid IRI");
let desc_pred = NamedNode::new("http://example.org/description").expect("valid IRI");
let age_pred = NamedNode::new("http://example.org/age").expect("valid IRI");
let desc_literal =
Literal::new_lang("Une description", "fr").expect("construction should succeed");
let age_literal = Literal::new_typed("25", crate::vocab::xsd::INTEGER.clone());
graph.insert(Triple::new(subject.clone(), desc_pred, desc_literal));
graph.insert(Triple::new(subject, age_pred, age_literal));
let serializer = Serializer::new(RdfFormat::Turtle);
let turtle = serializer
.serialize_graph(&graph)
.expect("operation should succeed");
assert!(turtle.contains("@fr"));
assert!(turtle.contains("^^xsd:integer"));
}
}