1use crate::{
208 model::{dataset::Dataset, graph::Graph, GraphName, Quad},
209 parser::RdfFormat,
210 Result,
211};
212
213pub struct Serializer {
215 format: RdfFormat,
216}
217
218impl Serializer {
219 pub fn new(format: RdfFormat) -> Self {
221 Serializer { format }
222 }
223
224 pub fn serialize_graph(&self, graph: &Graph) -> Result<String> {
226 match self.format {
227 RdfFormat::Turtle => self.serialize_turtle(graph),
228 RdfFormat::NTriples => self.serialize_ntriples(graph),
229 RdfFormat::TriG => self.serialize_trig_graph(graph),
230 RdfFormat::NQuads => self.serialize_nquads_graph(graph),
231 RdfFormat::RdfXml => self.serialize_rdfxml(graph),
232 RdfFormat::JsonLd => self.serialize_jsonld(graph),
233 }
234 }
235
236 pub fn serialize_dataset(&self, dataset: &Dataset) -> Result<String> {
238 match self.format {
239 RdfFormat::Turtle => Err(crate::OxirsError::Serialize(
240 "Turtle format does not support datasets (use TriG instead)".to_string(),
241 )),
242 RdfFormat::NTriples => Err(crate::OxirsError::Serialize(
243 "N-Triples format does not support datasets (use N-Quads instead)".to_string(),
244 )),
245 RdfFormat::TriG => self.serialize_trig_dataset(dataset),
246 RdfFormat::NQuads => self.serialize_nquads_dataset(dataset),
247 RdfFormat::RdfXml => Err(crate::OxirsError::Serialize(
248 "RDF/XML dataset serialization not yet implemented".to_string(),
249 )),
250 RdfFormat::JsonLd => Err(crate::OxirsError::Serialize(
251 "JSON-LD dataset serialization not yet implemented".to_string(),
252 )),
253 }
254 }
255
256 pub fn serialize(&self, graph: &Graph) -> Result<String> {
258 self.serialize_graph(graph)
259 }
260
261 fn serialize_turtle(&self, graph: &Graph) -> Result<String> {
262 let mut serializer = TurtleSerializer::new();
263 serializer.serialize_graph(graph)
264 }
265
266 fn serialize_ntriples(&self, graph: &Graph) -> Result<String> {
267 let mut result = String::new();
268
269 for triple in graph.iter() {
270 match triple.subject() {
272 crate::model::Subject::NamedNode(node) => {
273 result.push_str(&format!("<{}>", node.as_str()));
274 }
275 crate::model::Subject::BlankNode(node) => {
276 result.push_str(&format!("{node}"));
277 }
278 crate::model::Subject::Variable(_) => {
279 return Err(crate::OxirsError::Serialize(
280 "Variables not supported in N-Triples serialization".to_string(),
281 ));
282 }
283 crate::model::Subject::QuotedTriple(_) => {
284 return Err(crate::OxirsError::Serialize(
285 "Quoted triples not supported in N-Triples serialization".to_string(),
286 ));
287 }
288 }
289
290 result.push(' ');
291
292 match triple.predicate() {
294 crate::model::Predicate::NamedNode(node) => {
295 result.push_str(&format!("<{}>", node.as_str()));
296 }
297 crate::model::Predicate::Variable(_) => {
298 return Err(crate::OxirsError::Serialize(
299 "Variables not supported in N-Triples serialization".to_string(),
300 ));
301 }
302 }
303
304 result.push(' ');
305
306 match triple.object() {
308 crate::model::Object::NamedNode(node) => {
309 result.push_str(&format!("<{}>", node.as_str()));
310 }
311 crate::model::Object::BlankNode(node) => {
312 result.push_str(&format!("{node}"));
313 }
314 crate::model::Object::Literal(literal) => {
315 result.push('"');
316 for c in literal.value().chars() {
318 match c {
319 '"' => result.push_str("\\\""),
320 '\\' => result.push_str("\\\\"),
321 '\n' => result.push_str("\\n"),
322 '\r' => result.push_str("\\r"),
323 '\t' => result.push_str("\\t"),
324 _ => result.push(c),
325 }
326 }
327 result.push('"');
328
329 if let Some(lang) = literal.language() {
331 result.push_str(&format!("@{lang}"));
332 } else {
333 let datatype = literal.datatype();
334 if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
335 result.push_str(&format!("^^<{}>", datatype.as_str()));
336 }
337 }
338 }
339 crate::model::Object::Variable(_) => {
340 return Err(crate::OxirsError::Serialize(
341 "Variables not supported in N-Triples serialization".to_string(),
342 ));
343 }
344 crate::model::Object::QuotedTriple(_) => {
345 return Err(crate::OxirsError::Serialize(
346 "Quoted triples not supported in N-Triples serialization".to_string(),
347 ));
348 }
349 }
350
351 result.push_str(" .\n");
352 }
353
354 Ok(result)
355 }
356
357 fn serialize_rdfxml(&self, graph: &Graph) -> Result<String> {
358 use oxrdfxml::RdfXmlSerializer;
359
360 let mut output = Vec::new();
361 let mut serializer = RdfXmlSerializer::new().for_writer(&mut output);
362
363 for triple in graph.iter() {
365 let oxrdf_triple = self.convert_triple_to_oxrdf(triple)?;
367 serializer.serialize_triple(&oxrdf_triple).map_err(|e| {
368 crate::OxirsError::Serialize(format!("RDF/XML serialization error: {e}"))
369 })?;
370 }
371
372 serializer
373 .finish()
374 .map_err(|e| crate::OxirsError::Serialize(format!("RDF/XML finish error: {e}")))?;
375
376 String::from_utf8(output)
377 .map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
378 }
379
380 fn serialize_trig_graph(&self, graph: &Graph) -> Result<String> {
381 use oxttl::TriGSerializer;
382
383 let mut output = Vec::new();
384 let mut serializer = TriGSerializer::new().for_writer(&mut output);
385
386 for triple in graph.iter() {
388 let oxrdf_quad = self.convert_triple_to_oxrdf_quad(triple, None)?;
389 serializer.serialize_quad(&oxrdf_quad).map_err(|e| {
390 crate::OxirsError::Serialize(format!("TriG serialization error: {e}"))
391 })?;
392 }
393
394 serializer
395 .finish()
396 .map_err(|e| crate::OxirsError::Serialize(format!("TriG finish error: {e}")))?;
397
398 String::from_utf8(output)
399 .map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
400 }
401
402 fn serialize_trig_dataset(&self, dataset: &Dataset) -> Result<String> {
403 use oxttl::TriGSerializer;
404
405 let mut output = Vec::new();
406 let mut serializer = TriGSerializer::new().for_writer(&mut output);
407
408 for quad in dataset.iter() {
410 let oxrdf_quad = self.convert_quad_to_oxrdf(&quad)?;
411 serializer.serialize_quad(&oxrdf_quad).map_err(|e| {
412 crate::OxirsError::Serialize(format!("TriG serialization error: {e}"))
413 })?;
414 }
415
416 serializer
417 .finish()
418 .map_err(|e| crate::OxirsError::Serialize(format!("TriG finish error: {e}")))?;
419
420 String::from_utf8(output)
421 .map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
422 }
423
424 fn serialize_nquads_graph(&self, graph: &Graph) -> Result<String> {
425 let mut result = String::new();
427
428 for triple in graph.iter() {
429 result.push_str(&self.serialize_quad_to_nquads(&Quad::from_triple(triple.clone()))?);
430 }
431
432 Ok(result)
433 }
434
435 fn serialize_nquads_dataset(&self, dataset: &Dataset) -> Result<String> {
436 let mut result = String::new();
437
438 for quad in dataset.iter() {
439 result.push_str(&self.serialize_quad_to_nquads(&quad)?);
440 }
441
442 Ok(result)
443 }
444
445 pub fn serialize_quad_to_nquads(&self, quad: &Quad) -> Result<String> {
446 let mut result = String::new();
447
448 match quad.subject() {
450 crate::model::Subject::NamedNode(node) => {
451 result.push_str(&format!("<{}>", node.as_str()));
452 }
453 crate::model::Subject::BlankNode(node) => {
454 result.push_str(&format!("{node}"));
455 }
456 crate::model::Subject::Variable(_) => {
457 return Err(crate::OxirsError::Serialize(
458 "Variables not supported in N-Quads serialization".to_string(),
459 ));
460 }
461 crate::model::Subject::QuotedTriple(_) => {
462 return Err(crate::OxirsError::Serialize(
463 "Quoted triples not supported in N-Quads serialization".to_string(),
464 ));
465 }
466 }
467
468 result.push(' ');
469
470 match quad.predicate() {
472 crate::model::Predicate::NamedNode(node) => {
473 result.push_str(&format!("<{}>", node.as_str()));
474 }
475 crate::model::Predicate::Variable(_) => {
476 return Err(crate::OxirsError::Serialize(
477 "Variables not supported in N-Quads serialization".to_string(),
478 ));
479 }
480 }
481
482 result.push(' ');
483
484 match quad.object() {
486 crate::model::Object::NamedNode(node) => {
487 result.push_str(&format!("<{}>", node.as_str()));
488 }
489 crate::model::Object::BlankNode(node) => {
490 result.push_str(&format!("{node}"));
491 }
492 crate::model::Object::Literal(literal) => {
493 result.push('"');
494 for c in literal.value().chars() {
496 match c {
497 '"' => result.push_str("\\\""),
498 '\\' => result.push_str("\\\\"),
499 '\n' => result.push_str("\\n"),
500 '\r' => result.push_str("\\r"),
501 '\t' => result.push_str("\\t"),
502 _ => result.push(c),
503 }
504 }
505 result.push('"');
506
507 if let Some(lang) = literal.language() {
509 result.push_str(&format!("@{lang}"));
510 } else {
511 let datatype = literal.datatype();
512 if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
513 result.push_str(&format!("^^<{}>", datatype.as_str()));
514 }
515 }
516 }
517 crate::model::Object::Variable(_) => {
518 return Err(crate::OxirsError::Serialize(
519 "Variables not supported in N-Quads serialization".to_string(),
520 ));
521 }
522 crate::model::Object::QuotedTriple(_) => {
523 return Err(crate::OxirsError::Serialize(
524 "Quoted triples not supported in N-Quads serialization".to_string(),
525 ));
526 }
527 }
528
529 result.push(' ');
530
531 match quad.graph_name() {
533 GraphName::NamedNode(node) => {
534 result.push_str(&format!("<{}>", node.as_str()));
535 }
536 GraphName::BlankNode(node) => {
537 result.push_str(&format!("{node}"));
538 }
539 GraphName::Variable(_) => {
540 return Err(crate::OxirsError::Serialize(
541 "Variables not supported in N-Quads serialization".to_string(),
542 ));
543 }
544 GraphName::DefaultGraph => {
545 result.pop(); result.push_str(" .\n");
549 return Ok(result);
550 }
551 }
552
553 result.push_str(" .\n");
554
555 Ok(result)
556 }
557
558 fn serialize_jsonld(&self, graph: &Graph) -> Result<String> {
559 use oxjsonld::JsonLdSerializer;
560
561 let mut output = Vec::new();
562 let mut serializer = JsonLdSerializer::new().for_writer(&mut output);
563
564 for triple in graph.iter() {
566 let oxrdf_quad = self.convert_triple_to_oxrdf_quad(triple, None)?;
567 serializer.serialize_quad(&oxrdf_quad).map_err(|e| {
568 crate::OxirsError::Serialize(format!("JSON-LD serialization error: {e}"))
569 })?;
570 }
571
572 serializer
573 .finish()
574 .map_err(|e| crate::OxirsError::Serialize(format!("JSON-LD finish error: {e}")))?;
575
576 String::from_utf8(output)
577 .map_err(|e| crate::OxirsError::Serialize(format!("UTF-8 conversion error: {e}")))
578 }
579
580 fn convert_triple_to_oxrdf(&self, triple: &crate::model::Triple) -> Result<oxrdf::Triple> {
582 let subject = match triple.subject() {
584 crate::model::Subject::NamedNode(n) => {
585 oxrdf::NamedOrBlankNode::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
586 }
587 crate::model::Subject::BlankNode(b) => {
588 oxrdf::NamedOrBlankNode::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
589 }
590 _ => {
591 return Err(crate::OxirsError::Serialize(
592 "Variables and quoted triples not supported in serialization".to_string(),
593 ))
594 }
595 };
596
597 let predicate = match triple.predicate() {
599 crate::model::Predicate::NamedNode(n) => oxrdf::NamedNode::new_unchecked(n.as_str()),
600 _ => {
601 return Err(crate::OxirsError::Serialize(
602 "Variable predicates not supported in serialization".to_string(),
603 ))
604 }
605 };
606
607 let object = match triple.object() {
609 crate::model::Object::NamedNode(n) => {
610 oxrdf::Term::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
611 }
612 crate::model::Object::BlankNode(b) => {
613 oxrdf::Term::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
614 }
615 crate::model::Object::Literal(l) => {
616 let literal = if let Some(lang) = l.language() {
617 oxrdf::Literal::new_language_tagged_literal_unchecked(l.value(), lang)
618 } else {
619 let datatype = oxrdf::NamedNode::new_unchecked(l.datatype().as_str());
620 oxrdf::Literal::new_typed_literal(l.value(), datatype)
621 };
622 oxrdf::Term::Literal(literal)
623 }
624 _ => {
625 return Err(crate::OxirsError::Serialize(
626 "Variable objects and quoted triples not supported in serialization"
627 .to_string(),
628 ))
629 }
630 };
631
632 Ok(oxrdf::Triple::new(subject, predicate, object))
633 }
634
635 fn convert_triple_to_oxrdf_quad(
637 &self,
638 triple: &crate::model::Triple,
639 graph_name: Option<&oxrdf::GraphName>,
640 ) -> Result<oxrdf::Quad> {
641 let oxrdf_triple = self.convert_triple_to_oxrdf(triple)?;
642 let graph = graph_name
643 .cloned()
644 .unwrap_or(oxrdf::GraphName::DefaultGraph);
645 Ok(oxrdf::Quad::new(
646 oxrdf_triple.subject,
647 oxrdf_triple.predicate,
648 oxrdf_triple.object,
649 graph,
650 ))
651 }
652
653 fn convert_quad_to_oxrdf(&self, quad: &Quad) -> Result<oxrdf::Quad> {
655 let subject = match quad.subject() {
657 crate::model::Subject::NamedNode(n) => {
658 oxrdf::NamedOrBlankNode::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
659 }
660 crate::model::Subject::BlankNode(b) => {
661 oxrdf::NamedOrBlankNode::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
662 }
663 _ => {
664 return Err(crate::OxirsError::Serialize(
665 "Variables and quoted triples not supported in serialization".to_string(),
666 ))
667 }
668 };
669
670 let predicate = match quad.predicate() {
672 crate::model::Predicate::NamedNode(n) => oxrdf::NamedNode::new_unchecked(n.as_str()),
673 _ => {
674 return Err(crate::OxirsError::Serialize(
675 "Variable predicates not supported in serialization".to_string(),
676 ))
677 }
678 };
679
680 let object = match quad.object() {
682 crate::model::Object::NamedNode(n) => {
683 oxrdf::Term::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
684 }
685 crate::model::Object::BlankNode(b) => {
686 oxrdf::Term::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
687 }
688 crate::model::Object::Literal(l) => {
689 let literal = if let Some(lang) = l.language() {
690 oxrdf::Literal::new_language_tagged_literal_unchecked(l.value(), lang)
691 } else {
692 let datatype = oxrdf::NamedNode::new_unchecked(l.datatype().as_str());
693 oxrdf::Literal::new_typed_literal(l.value(), datatype)
694 };
695 oxrdf::Term::Literal(literal)
696 }
697 _ => {
698 return Err(crate::OxirsError::Serialize(
699 "Variable objects and quoted triples not supported in serialization"
700 .to_string(),
701 ))
702 }
703 };
704
705 let graph_name = match quad.graph_name() {
707 GraphName::NamedNode(n) => {
708 oxrdf::GraphName::NamedNode(oxrdf::NamedNode::new_unchecked(n.as_str()))
709 }
710 GraphName::BlankNode(b) => {
711 oxrdf::GraphName::BlankNode(oxrdf::BlankNode::new_unchecked(b.as_str()))
712 }
713 GraphName::DefaultGraph => oxrdf::GraphName::DefaultGraph,
714 _ => {
715 return Err(crate::OxirsError::Serialize(
716 "Variable graph names not supported in serialization".to_string(),
717 ))
718 }
719 };
720
721 Ok(oxrdf::Quad::new(subject, predicate, object, graph_name))
722 }
723}
724
725struct TurtleSerializer {
727 prefixes: std::collections::HashMap<String, String>,
728 used_namespaces: std::collections::HashSet<String>,
729}
730
731impl TurtleSerializer {
732 fn new() -> Self {
733 let mut prefixes = std::collections::HashMap::new();
734
735 prefixes.insert(
737 "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
738 "rdf".to_string(),
739 );
740 prefixes.insert(
741 "http://www.w3.org/2000/01/rdf-schema#".to_string(),
742 "rdfs".to_string(),
743 );
744 prefixes.insert(
745 "http://www.w3.org/2001/XMLSchema#".to_string(),
746 "xsd".to_string(),
747 );
748 prefixes.insert("http://xmlns.com/foaf/0.1/".to_string(), "foaf".to_string());
749 prefixes.insert(
750 "http://purl.org/dc/elements/1.1/".to_string(),
751 "dc".to_string(),
752 );
753
754 TurtleSerializer {
755 prefixes,
756 used_namespaces: std::collections::HashSet::new(),
757 }
758 }
759
760 fn serialize_graph(&mut self, graph: &Graph) -> Result<String> {
761 self.collect_namespaces(graph);
763
764 let mut result = String::new();
765
766 let mut prefix_entries: Vec<_> = self.prefixes.iter().collect();
768 prefix_entries.sort_by_key(|(_, prefix)| *prefix);
769
770 for (namespace, prefix) in prefix_entries {
771 if self.used_namespaces.contains(namespace) {
772 result.push_str(&format!("@prefix {prefix}: <{namespace}> .\n"));
773 }
774 }
775
776 if !self.prefixes.is_empty() && !graph.is_empty() {
777 result.push('\n');
778 }
779
780 let mut subjects_map: std::collections::HashMap<
782 crate::model::Subject,
783 Vec<&crate::model::Triple>,
784 > = std::collections::HashMap::new();
785
786 for triple in graph.iter() {
787 subjects_map
788 .entry(triple.subject().clone())
789 .or_default()
790 .push(triple);
791 }
792
793 let mut subject_entries: Vec<_> = subjects_map.iter().collect();
794 subject_entries.sort_by_key(|(subject, _)| format!("{subject}"));
795
796 for (i, (subject, triples)) in subject_entries.iter().enumerate() {
797 if i > 0 {
798 result.push('\n');
799 }
800
801 result.push_str(&self.serialize_subject(subject)?);
802
803 let mut predicates_map: std::collections::HashMap<
805 crate::model::Predicate,
806 Vec<&crate::model::Object>,
807 > = std::collections::HashMap::new();
808
809 for triple in triples.iter() {
810 predicates_map
811 .entry(triple.predicate().clone())
812 .or_default()
813 .push(triple.object());
814 }
815
816 let mut predicate_entries: Vec<_> = predicates_map.iter().collect();
817 predicate_entries.sort_by_key(|(predicate, _)| format!("{predicate}"));
818
819 for (j, (predicate, objects)) in predicate_entries.iter().enumerate() {
820 if j == 0 {
821 result.push(' ');
822 } else {
823 result.push_str(" ;\n ");
824 }
825
826 result.push_str(&self.serialize_predicate(predicate)?);
827 result.push(' ');
828
829 for (k, object) in objects.iter().enumerate() {
831 if k > 0 {
832 result.push_str(", ");
833 }
834 result.push_str(&self.serialize_object(object)?);
835 }
836 }
837
838 result.push_str(" .\n");
839 }
840
841 Ok(result)
842 }
843
844 fn collect_namespaces(&mut self, graph: &Graph) {
845 for triple in graph.iter() {
846 self.mark_namespace_used(triple.subject());
847 self.mark_namespace_used_predicate(triple.predicate());
848 self.mark_namespace_used_object(triple.object());
849 }
850 }
851
852 fn mark_namespace_used(&mut self, subject: &crate::model::Subject) {
853 if let crate::model::Subject::NamedNode(node) = subject {
854 if let Some(namespace) = self.extract_namespace(node.as_str()) {
855 self.used_namespaces.insert(namespace);
856 }
857 }
858 }
859
860 fn mark_namespace_used_predicate(&mut self, predicate: &crate::model::Predicate) {
861 if let crate::model::Predicate::NamedNode(node) = predicate {
862 if let Some(namespace) = self.extract_namespace(node.as_str()) {
863 self.used_namespaces.insert(namespace);
864 }
865 }
866 }
867
868 fn mark_namespace_used_object(&mut self, object: &crate::model::Object) {
869 match object {
870 crate::model::Object::NamedNode(node) => {
871 if let Some(namespace) = self.extract_namespace(node.as_str()) {
872 self.used_namespaces.insert(namespace);
873 }
874 }
875 crate::model::Object::Literal(literal) => {
876 let datatype = literal.datatype();
877 if let Some(namespace) = self.extract_namespace(datatype.as_str()) {
878 self.used_namespaces.insert(namespace);
879 }
880 }
881 _ => {}
882 }
883 }
884
885 fn extract_namespace(&self, iri: &str) -> Option<String> {
886 if let Some(hash_pos) = iri.rfind('#') {
888 Some(format!("{}#", &iri[..hash_pos]))
889 } else {
890 iri.rfind('/')
891 .map(|slash_pos| format!("{}/", &iri[..slash_pos]))
892 }
893 }
894
895 fn serialize_subject(&self, subject: &crate::model::Subject) -> Result<String> {
896 match subject {
897 crate::model::Subject::NamedNode(node) => self.serialize_iri(node.as_str()),
898 crate::model::Subject::BlankNode(node) => Ok(node.as_str().to_string()),
899 crate::model::Subject::Variable(_var) => Err(crate::OxirsError::Serialize(
900 "Variables not supported in Turtle serialization".to_string(),
901 )),
902 crate::model::Subject::QuotedTriple(qt) => Ok(format!(
903 "<< {} {} {} >>",
904 self.serialize_subject(qt.subject())?,
905 self.serialize_predicate(qt.predicate())?,
906 self.serialize_object(qt.object())?
907 )),
908 }
909 }
910
911 fn serialize_predicate(&self, predicate: &crate::model::Predicate) -> Result<String> {
912 match predicate {
913 crate::model::Predicate::NamedNode(node) => {
914 if node.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
916 Ok("a".to_string())
917 } else {
918 self.serialize_iri(node.as_str())
919 }
920 }
921 crate::model::Predicate::Variable(_) => Err(crate::OxirsError::Serialize(
922 "Variables not supported in Turtle serialization".to_string(),
923 )),
924 }
925 }
926
927 fn serialize_object(&self, object: &crate::model::Object) -> Result<String> {
928 match object {
929 crate::model::Object::NamedNode(node) => self.serialize_iri(node.as_str()),
930 crate::model::Object::BlankNode(node) => Ok(node.as_str().to_string()),
931 crate::model::Object::Literal(literal) => self.serialize_literal(literal),
932 crate::model::Object::Variable(_) => Err(crate::OxirsError::Serialize(
933 "Variables not supported in Turtle serialization".to_string(),
934 )),
935 crate::model::Object::QuotedTriple(qt) => Ok(format!(
936 "<< {} {} {} >>",
937 self.serialize_subject(qt.subject())?,
938 self.serialize_predicate(qt.predicate())?,
939 self.serialize_object(qt.object())?
940 )),
941 }
942 }
943
944 fn serialize_iri(&self, iri: &str) -> Result<String> {
945 for (namespace, prefix) in &self.prefixes {
947 if iri.starts_with(namespace) && self.used_namespaces.contains(namespace) {
948 let local_name = &iri[namespace.len()..];
949 if self.is_valid_local_name(local_name) {
951 return Ok(format!("{prefix}:{local_name}"));
952 }
953 }
954 }
955
956 Ok(format!("<{iri}>"))
958 }
959
960 fn is_valid_local_name(&self, name: &str) -> bool {
961 if name.is_empty() {
964 return true; }
966
967 let first_char = name
968 .chars()
969 .next()
970 .expect("non-empty name should have first char");
971 if !first_char.is_alphabetic() && first_char != '_' {
972 return false;
973 }
974
975 name.chars()
976 .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
977 }
978
979 fn serialize_literal(&self, literal: &crate::model::Literal) -> Result<String> {
980 let mut result = String::new();
981
982 result.push('"');
984 for c in literal.value().chars() {
985 match c {
986 '"' => result.push_str("\\\""),
987 '\\' => result.push_str("\\\\"),
988 '\n' => result.push_str("\\n"),
989 '\r' => result.push_str("\\r"),
990 '\t' => result.push_str("\\t"),
991 _ => result.push(c),
992 }
993 }
994 result.push('"');
995
996 if let Some(lang) = literal.language() {
998 result.push_str(&format!("@{lang}"));
999 } else {
1000 let datatype = literal.datatype();
1001 if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
1003 result.push_str("^^");
1004 result.push_str(&self.serialize_iri(datatype.as_str())?);
1005 }
1006 }
1007
1008 Ok(result)
1009 }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014 use super::*;
1015 use crate::model::graph::Graph;
1016 use crate::model::*;
1017
1018 fn create_test_graph() -> Graph {
1019 let mut graph = Graph::new();
1020
1021 let subject = NamedNode::new("http://example.org/alice").expect("valid IRI");
1023 let predicate = NamedNode::new("http://xmlns.com/foaf/0.1/name").expect("valid IRI");
1024 let object = Literal::new("Alice Smith");
1025 let triple1 = Triple::new(subject.clone(), predicate, object);
1026
1027 let age_pred = NamedNode::new("http://xmlns.com/foaf/0.1/age").expect("valid IRI");
1029 let age_obj = Literal::new_typed("30", crate::vocab::xsd::INTEGER.clone());
1030 let triple2 = Triple::new(subject.clone(), age_pred, age_obj);
1031
1032 let desc_pred = NamedNode::new("http://example.org/description").expect("valid IRI");
1034 let desc_obj =
1035 Literal::new_lang("Une personne", "fr").expect("construction should succeed");
1036 let triple3 = Triple::new(subject, desc_pred, desc_obj);
1037
1038 let blank_subject = BlankNode::new("person1").expect("valid blank node id");
1040 let knows_pred = NamedNode::new("http://xmlns.com/foaf/0.1/knows").expect("valid IRI");
1041 let knows_obj = NamedNode::new("http://example.org/bob").expect("valid IRI");
1042 let triple4 = Triple::new(blank_subject, knows_pred, knows_obj);
1043
1044 graph.insert(triple1);
1045 graph.insert(triple2);
1046 graph.insert(triple3);
1047 graph.insert(triple4);
1048
1049 graph
1050 }
1051
1052 #[test]
1053 fn test_ntriples_serialization() {
1054 let graph = create_test_graph();
1055 let serializer = Serializer::new(RdfFormat::NTriples);
1056
1057 let result = serializer.serialize_graph(&graph);
1058 assert!(result.is_ok());
1059
1060 let ntriples = result.expect("should have value");
1061 assert!(!ntriples.is_empty());
1062
1063 for line in ntriples.lines() {
1065 if !line.trim().is_empty() {
1066 assert!(line.ends_with(" ."), "Line should end with ' .': {line}");
1067 }
1068 }
1069
1070 assert!(ntriples.contains("http://example.org/alice"));
1072 assert!(ntriples.contains("http://xmlns.com/foaf/0.1/name"));
1073 assert!(ntriples.contains("\"Alice Smith\""));
1074 assert!(ntriples.contains("\"30\"^^<http://www.w3.org/2001/XMLSchema#integer>"));
1075 assert!(ntriples.contains("\"Une personne\"@fr"));
1076 assert!(ntriples.contains("_:person1"));
1077 }
1078
1079 #[test]
1080 fn test_literal_escaping() {
1081 let mut graph = Graph::new();
1082 let subject = NamedNode::new("http://example.org/test").expect("valid IRI");
1083 let predicate = NamedNode::new("http://example.org/description").expect("valid IRI");
1084
1085 let object = Literal::new("Text with \"quotes\" and \n newlines \t and tabs");
1087 let triple = Triple::new(subject, predicate, object);
1088 graph.insert(triple);
1089
1090 let serializer = Serializer::new(RdfFormat::NTriples);
1091 let result = serializer
1092 .serialize_graph(&graph)
1093 .expect("operation should succeed");
1094
1095 assert!(result.contains("\\\"quotes\\\""));
1097 assert!(result.contains("\\n"));
1098 assert!(result.contains("\\t"));
1099 }
1100
1101 #[test]
1102 fn test_empty_graph_serialization() {
1103 let graph = Graph::new();
1104 let serializer = Serializer::new(RdfFormat::NTriples);
1105
1106 let result = serializer.serialize_graph(&graph);
1107 assert!(result.is_ok());
1108
1109 let ntriples = result.expect("should have value");
1110 assert!(ntriples.is_empty());
1111 }
1112
1113 #[test]
1114 fn test_variable_serialization_error() {
1115 let mut graph = Graph::new();
1116 let variable_subject = Variable::new("x").expect("valid variable name");
1117 let predicate = NamedNode::new("http://example.org/predicate").expect("valid IRI");
1118 let object = Literal::new("test");
1119
1120 let triple = Triple::new(variable_subject, predicate, object);
1121 graph.insert(triple);
1122
1123 let serializer = Serializer::new(RdfFormat::NTriples);
1124 let result = serializer.serialize_graph(&graph);
1125
1126 assert!(result.is_err());
1127 assert!(result
1128 .unwrap_err()
1129 .to_string()
1130 .contains("Variables not supported"));
1131 }
1132
1133 #[test]
1134 fn test_turtle_serialization() {
1135 let graph = create_test_graph();
1136 let serializer = Serializer::new(RdfFormat::Turtle);
1137
1138 let result = serializer.serialize_graph(&graph);
1139 assert!(result.is_ok());
1140
1141 let turtle = result.expect("should have value");
1142 assert!(!turtle.is_empty());
1143
1144 assert!(turtle.contains("@prefix"));
1146
1147 assert!(turtle.ends_with(" .\n") || turtle.ends_with("."));
1152 }
1153
1154 #[test]
1155 fn test_turtle_serialization_with_prefixes() {
1156 let mut graph = Graph::new();
1157
1158 let alice = NamedNode::new("http://example.org/alice").expect("valid IRI");
1160 let name_pred = NamedNode::new("http://xmlns.com/foaf/0.1/name").expect("valid IRI");
1161 let person_type = NamedNode::new("http://xmlns.com/foaf/0.1/Person").expect("valid IRI");
1162 let rdf_type =
1163 NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").expect("valid IRI");
1164
1165 let name_literal = Literal::new("Alice");
1166
1167 graph.insert(Triple::new(alice.clone(), name_pred, name_literal));
1168 graph.insert(Triple::new(alice, rdf_type, person_type));
1169
1170 let serializer = Serializer::new(RdfFormat::Turtle);
1171 let turtle = serializer
1172 .serialize_graph(&graph)
1173 .expect("operation should succeed");
1174
1175 assert!(turtle.contains("@prefix foaf: <http://xmlns.com/foaf/0.1/>"));
1177
1178 assert!(turtle.contains(" a "));
1180
1181 assert!(turtle.contains("foaf:"));
1183 }
1184
1185 #[test]
1186 fn test_turtle_serialization_abbreviated_syntax() {
1187 let mut graph = Graph::new();
1188
1189 let alice = NamedNode::new("http://example.org/alice").expect("valid IRI");
1190 let name_pred = NamedNode::new("http://xmlns.com/foaf/0.1/name").expect("valid IRI");
1191 let age_pred = NamedNode::new("http://xmlns.com/foaf/0.1/age").expect("valid IRI");
1192
1193 let name_literal = Literal::new("Alice");
1194 let age_literal = Literal::new_typed("30", crate::vocab::xsd::INTEGER.clone());
1195
1196 graph.insert(Triple::new(alice.clone(), name_pred, name_literal));
1197 graph.insert(Triple::new(alice, age_pred, age_literal));
1198
1199 let serializer = Serializer::new(RdfFormat::Turtle);
1200 let turtle = serializer
1201 .serialize_graph(&graph)
1202 .expect("operation should succeed");
1203
1204 assert!(turtle.contains(";"));
1206
1207 assert!(turtle.lines().count() >= 3); }
1210
1211 #[test]
1212 fn test_turtle_serialization_literals() {
1213 let mut graph = Graph::new();
1214
1215 let subject = NamedNode::new("http://example.org/test").expect("valid IRI");
1216 let desc_pred = NamedNode::new("http://example.org/description").expect("valid IRI");
1217 let age_pred = NamedNode::new("http://example.org/age").expect("valid IRI");
1218
1219 let desc_literal =
1221 Literal::new_lang("Une description", "fr").expect("construction should succeed");
1222 let age_literal = Literal::new_typed("25", crate::vocab::xsd::INTEGER.clone());
1224
1225 graph.insert(Triple::new(subject.clone(), desc_pred, desc_literal));
1226 graph.insert(Triple::new(subject, age_pred, age_literal));
1227
1228 let serializer = Serializer::new(RdfFormat::Turtle);
1229 let turtle = serializer
1230 .serialize_graph(&graph)
1231 .expect("operation should succeed");
1232
1233 assert!(turtle.contains("@fr"));
1235
1236 assert!(turtle.contains("^^xsd:integer"));
1238 }
1239}