Skip to main content

oxihuman_export/
turtle_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Turtle (Terse RDF Triple Language) export.
6
7/// An RDF triple.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct RdfTriple {
11    pub subject: String,
12    pub predicate: String,
13    pub object: String,
14}
15
16/// A Turtle document.
17#[allow(dead_code)]
18#[derive(Debug, Clone, Default)]
19pub struct TurtleDoc {
20    pub prefixes: Vec<(String, String)>,
21    pub triples: Vec<RdfTriple>,
22}
23
24impl TurtleDoc {
25    #[allow(dead_code)]
26    pub fn new() -> Self {
27        TurtleDoc::default()
28    }
29
30    #[allow(dead_code)]
31    pub fn add_prefix(&mut self, prefix: &str, iri: &str) {
32        self.prefixes.push((prefix.to_string(), iri.to_string()));
33    }
34
35    #[allow(dead_code)]
36    pub fn add_triple(&mut self, s: &str, p: &str, o: &str) {
37        self.triples.push(RdfTriple {
38            subject: s.to_string(),
39            predicate: p.to_string(),
40            object: o.to_string(),
41        });
42    }
43}
44
45/// Serialize a Turtle document to text.
46#[allow(dead_code)]
47pub fn export_turtle(doc: &TurtleDoc) -> String {
48    let mut out = String::new();
49    for (prefix, iri) in &doc.prefixes {
50        out.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri));
51    }
52    if !doc.prefixes.is_empty() && !doc.triples.is_empty() {
53        out.push('\n');
54    }
55    for t in &doc.triples {
56        out.push_str(&format!("{} {} {} .\n", t.subject, t.predicate, t.object));
57    }
58    out
59}
60
61/// Triple count.
62#[allow(dead_code)]
63pub fn triple_count(doc: &TurtleDoc) -> usize {
64    doc.triples.len()
65}
66
67/// Prefix count.
68#[allow(dead_code)]
69pub fn prefix_count(doc: &TurtleDoc) -> usize {
70    doc.prefixes.len()
71}
72
73/// Check if output contains a specific triple.
74#[allow(dead_code)]
75pub fn contains_triple(doc: &TurtleDoc, s: &str, p: &str) -> bool {
76    doc.triples
77        .iter()
78        .any(|t| t.subject == s && t.predicate == p)
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn new_doc_empty() {
87        let doc = TurtleDoc::new();
88        assert_eq!(triple_count(&doc), 0);
89    }
90
91    #[test]
92    fn add_triple_count() {
93        let mut doc = TurtleDoc::new();
94        doc.add_triple("<http://a>", "<http://b>", "<http://c>");
95        assert_eq!(triple_count(&doc), 1);
96    }
97
98    #[test]
99    fn add_prefix_count() {
100        let mut doc = TurtleDoc::new();
101        doc.add_prefix("schema", "http://schema.org/");
102        assert_eq!(prefix_count(&doc), 1);
103    }
104
105    #[test]
106    fn export_contains_prefix() {
107        let mut doc = TurtleDoc::new();
108        doc.add_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
109        let s = export_turtle(&doc);
110        assert!(s.contains("@prefix rdf:"));
111    }
112
113    #[test]
114    fn export_contains_triple() {
115        let mut doc = TurtleDoc::new();
116        doc.add_triple("<http://a>", "<http://b>", "<http://c>");
117        let s = export_turtle(&doc);
118        assert!(s.contains("<http://a>"));
119    }
120
121    #[test]
122    fn triple_ends_with_dot() {
123        let mut doc = TurtleDoc::new();
124        doc.add_triple("<http://a>", "<http://b>", "\"value\"");
125        let s = export_turtle(&doc);
126        assert!(s.contains(" ."));
127    }
128
129    #[test]
130    fn contains_triple_helper() {
131        let mut doc = TurtleDoc::new();
132        doc.add_triple("<http://a>", "rdf:type", "<http://Person>");
133        assert!(contains_triple(&doc, "<http://a>", "rdf:type"));
134    }
135
136    #[test]
137    fn not_contains_nonexistent_triple() {
138        let doc = TurtleDoc::new();
139        assert!(!contains_triple(&doc, "<http://x>", "rdf:type"));
140    }
141
142    #[test]
143    fn empty_doc_empty_string() {
144        let doc = TurtleDoc::new();
145        assert!(export_turtle(&doc).is_empty());
146    }
147
148    #[test]
149    fn multiple_triples() {
150        let mut doc = TurtleDoc::new();
151        doc.add_triple("a", "b", "c");
152        doc.add_triple("d", "e", "f");
153        let s = export_turtle(&doc);
154        assert!(s.contains("a b c ."));
155        assert!(s.contains("d e f ."));
156    }
157}