1use super::error::SerializeResult;
7use super::error::{ParseResult, RdfParseError};
8use super::serializer::QuadSerializer;
9use crate::model::{QuadRef, Triple, TripleRef};
10use std::collections::HashMap;
11use std::io::{Read, Write};
12
13#[derive(Debug, Clone)]
15pub struct TurtleParser {
16 lenient: bool,
17 base_iri: Option<String>,
18 prefixes: HashMap<String, String>,
19}
20
21impl TurtleParser {
22 pub fn new() -> Self {
24 Self {
25 lenient: false,
26 base_iri: None,
27 prefixes: HashMap::new(),
28 }
29 }
30
31 pub fn lenient(mut self) -> Self {
33 self.lenient = true;
34 self
35 }
36
37 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Self {
39 self.base_iri = Some(base_iri.into());
40 self
41 }
42
43 pub fn with_prefix(mut self, prefix: impl Into<String>, iri: impl Into<String>) -> Self {
45 self.prefixes.insert(prefix.into(), iri.into());
46 self
47 }
48
49 pub fn parse_reader<R: Read>(&self, mut reader: R) -> ParseResult<Vec<Triple>> {
51 let mut buffer = String::new();
53 reader.read_to_string(&mut buffer)?;
54
55 self.parse_str(&buffer)
59 }
60
61 pub fn parse_slice(&self, slice: &[u8]) -> ParseResult<Vec<Triple>> {
63 let content = std::str::from_utf8(slice)
66 .map_err(|e| RdfParseError::syntax(format!("Invalid UTF-8: {e}")))?;
67
68 self.parse_str(content)
69 }
70
71 pub fn parse_str(&self, input: &str) -> ParseResult<Vec<Triple>> {
73 use super::parser::helpers::convert_quad;
74 use std::io::Cursor;
75
76 let mut oxttl_parser = oxttl::TurtleParser::new();
78
79 if let Some(ref base) = self.base_iri {
81 oxttl_parser = oxttl_parser
82 .with_base_iri(base.as_str())
83 .unwrap_or_else(|_| oxttl::TurtleParser::new());
84 }
85
86 if self.lenient {
88 oxttl_parser = oxttl_parser.lenient();
89 }
90
91 let reader = Cursor::new(input.as_bytes());
93 let mut triples = Vec::new();
94
95 for result in oxttl_parser.for_reader(reader) {
96 match result {
97 Ok(triple) => {
98 let quad = oxrdf::Quad::new(
100 triple.subject,
101 triple.predicate,
102 triple.object,
103 oxrdf::GraphName::DefaultGraph,
104 );
105 let oxirs_quad = convert_quad(quad)?;
106 triples.push(oxirs_quad.to_triple());
107 }
108 Err(e) => {
109 if !self.lenient {
110 return Err(RdfParseError::syntax(e.to_string()));
111 }
112 }
114 }
115 }
116
117 Ok(triples)
118 }
119
120 pub fn prefixes(&self) -> &HashMap<String, String> {
122 &self.prefixes
123 }
124
125 pub fn base_iri(&self) -> Option<&str> {
127 self.base_iri.as_deref()
128 }
129
130 pub fn is_lenient(&self) -> bool {
132 self.lenient
133 }
134}
135
136impl Default for TurtleParser {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct TurtleSerializer {
145 base_iri: Option<String>,
146 prefixes: HashMap<String, String>,
147 pretty: bool,
148}
149
150impl TurtleSerializer {
151 pub fn new() -> Self {
153 Self {
154 base_iri: None,
155 prefixes: HashMap::new(),
156 pretty: false,
157 }
158 }
159
160 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Self {
162 self.base_iri = Some(base_iri.into());
163 self
164 }
165
166 pub fn with_prefix(mut self, prefix: impl Into<String>, iri: impl Into<String>) -> Self {
168 self.prefixes.insert(prefix.into(), iri.into());
169 self
170 }
171
172 pub fn pretty(mut self) -> Self {
174 self.pretty = true;
175 self
176 }
177
178 pub fn for_writer<W: Write>(self, writer: W) -> WriterTurtleSerializer<W> {
180 WriterTurtleSerializer::new(writer, self)
181 }
182
183 pub fn serialize_to_string(&self, triples: &[Triple]) -> SerializeResult<String> {
185 let mut buffer = Vec::new();
186 {
187 let mut serializer = self.clone().for_writer(&mut buffer);
188 for triple in triples {
189 serializer.serialize_triple(triple.as_ref())?;
190 }
191 serializer.finish()?;
192 }
193 String::from_utf8(buffer)
194 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
195 }
196
197 pub fn prefixes(&self) -> &HashMap<String, String> {
199 &self.prefixes
200 }
201
202 pub fn base_iri(&self) -> Option<&str> {
204 self.base_iri.as_deref()
205 }
206
207 pub fn is_pretty(&self) -> bool {
209 self.pretty
210 }
211}
212
213impl Default for TurtleSerializer {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219pub struct WriterTurtleSerializer<W: Write> {
221 writer: W,
222 config: TurtleSerializer,
223 headers_written: bool,
224}
225
226impl<W: Write> WriterTurtleSerializer<W> {
227 pub fn new(writer: W, config: TurtleSerializer) -> Self {
229 Self {
230 writer,
231 config,
232 headers_written: false,
233 }
234 }
235
236 pub fn serialize_triple(&mut self, triple: TripleRef<'_>) -> SerializeResult<()> {
238 self.ensure_headers_written()?;
239
240 let subject_str = self.serialize_subject(triple.subject())?;
242
243 let predicate_str = self.serialize_predicate(triple.predicate())?;
245
246 let object_str = self.serialize_object(triple.object())?;
248
249 if self.config.pretty {
251 writeln!(self.writer, "{subject_str} {predicate_str} {object_str} .")?;
252 } else {
253 writeln!(self.writer, "{subject_str} {predicate_str} {object_str}.")?;
254 }
255
256 Ok(())
257 }
258
259 fn serialize_subject(&self, subject: crate::model::SubjectRef<'_>) -> SerializeResult<String> {
261 use crate::model::SubjectRef;
262
263 match subject {
264 SubjectRef::NamedNode(node) => self.serialize_named_node(node.into()),
265 SubjectRef::BlankNode(node) => {
266 let node_str = node.as_str();
267 Ok(format!("_:{node_str}"))
268 }
269 SubjectRef::Variable(var) => {
270 let var_str = var.as_str();
271 Ok(format!("?{var_str}"))
272 }
273 SubjectRef::QuotedTriple(qt) => self.serialize_quoted_triple(qt.inner()),
274 }
275 }
276
277 fn serialize_quoted_triple(&self, inner: &crate::model::Triple) -> SerializeResult<String> {
279 let s = self.serialize_subject(inner.subject().into())?;
280 let p = self.serialize_predicate(inner.predicate().into())?;
281 let o = self.serialize_object(inner.object().into())?;
282 Ok(format!("<< {s} {p} {o} >>"))
283 }
284
285 fn serialize_predicate(
287 &self,
288 predicate: crate::model::PredicateRef<'_>,
289 ) -> SerializeResult<String> {
290 use crate::model::PredicateRef;
291
292 match predicate {
293 PredicateRef::NamedNode(node) => {
294 if node.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
296 Ok("a".to_string())
297 } else {
298 self.serialize_named_node(node.into())
299 }
300 }
301 PredicateRef::Variable(var) => {
302 let var_str = var.as_str();
303 Ok(format!("?{var_str}"))
304 }
305 }
306 }
307
308 fn serialize_object(&self, object: crate::model::ObjectRef<'_>) -> SerializeResult<String> {
310 use crate::model::ObjectRef;
311
312 match object {
313 ObjectRef::NamedNode(node) => self.serialize_named_node(node.into()),
314 ObjectRef::BlankNode(node) => {
315 let node_str = node.as_str();
316 Ok(format!("_:{node_str}"))
317 }
318 ObjectRef::Literal(literal) => self.serialize_literal(literal),
319 ObjectRef::Variable(var) => {
320 let var_str = var.as_str();
321 Ok(format!("?{var_str}"))
322 }
323 ObjectRef::QuotedTriple(qt) => self.serialize_quoted_triple(qt.inner()),
324 }
325 }
326
327 fn serialize_named_node(
329 &self,
330 node: crate::model::NamedNodeRef<'_>,
331 ) -> SerializeResult<String> {
332 let iri = node.as_str();
333
334 for (prefix, namespace) in &self.config.prefixes {
336 if iri.starts_with(namespace) {
337 let local = &iri[namespace.len()..];
338 if is_valid_local_name(local) {
340 return Ok(format!("{prefix}:{local}"));
341 }
342 }
343 }
344
345 Ok(format!("<{iri}>"))
347 }
348
349 fn serialize_literal(&self, literal: &crate::model::Literal) -> SerializeResult<String> {
351 let value = literal.value();
352
353 let escaped_value = escape_turtle_string(value);
355
356 if let Some(lang) = literal.language() {
358 return Ok(format!("\"{escaped_value}\"@{lang}"));
359 }
360
361 let datatype = literal.datatype();
363 if datatype.as_str() == "http://www.w3.org/2001/XMLSchema#string" {
364 Ok(format!("\"{escaped_value}\""))
366 } else {
367 let datatype_str = self.serialize_named_node(datatype)?;
369 Ok(format!("\"{escaped_value}\"^^{datatype_str}"))
370 }
371 }
372
373 pub fn finish(self) -> SerializeResult<W> {
375 Ok(self.writer)
376 }
377
378 fn ensure_headers_written(&mut self) -> SerializeResult<()> {
380 if self.headers_written {
381 return Ok(());
382 }
383
384 if let Some(base) = &self.config.base_iri {
386 writeln!(self.writer, "@base <{base}> .")?;
387 }
388
389 for (prefix, iri) in &self.config.prefixes {
391 writeln!(self.writer, "@prefix {prefix}: <{iri}> .")?;
392 }
393
394 if self.config.base_iri.is_some() || !self.config.prefixes.is_empty() {
396 writeln!(self.writer)?;
397 }
398
399 self.headers_written = true;
400 Ok(())
401 }
402}
403
404impl<W: Write> QuadSerializer<W> for WriterTurtleSerializer<W> {
405 fn serialize_quad(&mut self, quad: QuadRef<'_>) -> SerializeResult<()> {
406 if quad.graph_name().is_default_graph() {
411 self.serialize_triple(quad.triple())
412 } else {
413 Err(std::io::Error::new(
414 std::io::ErrorKind::Unsupported,
415 format!(
416 "Turtle cannot represent named graphs; quad has graph name {}. \
417 Use TriG or N-Quads to serialize datasets with named graphs.",
418 quad.graph_name()
419 ),
420 ))
421 }
422 }
423
424 fn finish(self: Box<Self>) -> SerializeResult<W> {
425 Ok(self.writer)
426 }
427}
428
429fn is_valid_local_name(local: &str) -> bool {
431 if local.is_empty() {
432 return true; }
434
435 let first_char = local
437 .chars()
438 .next()
439 .expect("local name validated to be non-empty");
440 if !is_pn_chars_base(first_char) && first_char != '_' {
441 return false;
442 }
443
444 for ch in local.chars().skip(1) {
446 if !is_pn_chars(ch) && ch != '.' && ch != '-' {
447 return false;
448 }
449 }
450
451 !local.ends_with('.')
453}
454
455fn is_pn_chars_base(ch: char) -> bool {
457 ch.is_ascii_alphabetic()
458 || ('\u{00C0}'..='\u{00D6}').contains(&ch)
459 || ('\u{00D8}'..='\u{00F6}').contains(&ch)
460 || ('\u{00F8}'..='\u{02FF}').contains(&ch)
461 || ('\u{0370}'..='\u{037D}').contains(&ch)
462 || ('\u{037F}'..='\u{1FFF}').contains(&ch)
463 || ('\u{200C}'..='\u{200D}').contains(&ch)
464 || ('\u{2070}'..='\u{218F}').contains(&ch)
465 || ('\u{2C00}'..='\u{2FEF}').contains(&ch)
466 || ('\u{3001}'..='\u{D7FF}').contains(&ch)
467 || ('\u{F900}'..='\u{FDCF}').contains(&ch)
468 || ('\u{FDF0}'..='\u{FFFD}').contains(&ch)
469}
470
471fn is_pn_chars(ch: char) -> bool {
473 is_pn_chars_base(ch)
474 || ch == '_'
475 || ch.is_ascii_digit()
476 || ch == '\u{00B7}'
477 || ('\u{0300}'..='\u{036F}').contains(&ch)
478 || ('\u{203F}'..='\u{2040}').contains(&ch)
479}
480
481fn escape_turtle_string(input: &str) -> String {
483 let mut result = String::with_capacity(input.len());
484
485 for ch in input.chars() {
486 match ch {
487 '"' => result.push_str("\\\""),
488 '\\' => result.push_str("\\\\"),
489 '\n' => result.push_str("\\n"),
490 '\r' => result.push_str("\\r"),
491 '\t' => result.push_str("\\t"),
492 '\x08' => result.push_str("\\b"), '\x0C' => result.push_str("\\f"), c if c.is_control() => {
495 let code = c as u32;
497 result.push_str(&format!("\\u{code:04X}"));
498 }
499 c => result.push(c),
500 }
501 }
502
503 result
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn test_turtle_parser_creation() {
512 let parser = TurtleParser::new();
513 assert!(!parser.is_lenient());
514 assert!(parser.base_iri().is_none());
515 assert!(parser.prefixes().is_empty());
516 }
517
518 #[test]
519 fn test_turtle_parser_configuration() {
520 let parser = TurtleParser::new()
521 .lenient()
522 .with_base_iri("http://example.org/")
523 .with_prefix("ex", "http://example.org/ns#");
524
525 assert!(parser.is_lenient());
526 assert_eq!(parser.base_iri(), Some("http://example.org/"));
527 assert_eq!(
528 parser.prefixes().get("ex"),
529 Some(&"http://example.org/ns#".to_string())
530 );
531 }
532
533 #[test]
534 fn test_turtle_serializer_creation() {
535 let serializer = TurtleSerializer::new();
536 assert!(!serializer.is_pretty());
537 assert!(serializer.base_iri().is_none());
538 assert!(serializer.prefixes().is_empty());
539 }
540
541 #[test]
542 fn test_turtle_serializer_configuration() {
543 let serializer = TurtleSerializer::new()
544 .pretty()
545 .with_base_iri("http://example.org/")
546 .with_prefix("ex", "http://example.org/ns#");
547
548 assert!(serializer.is_pretty());
549 assert_eq!(serializer.base_iri(), Some("http://example.org/"));
550 assert_eq!(
551 serializer.prefixes().get("ex"),
552 Some(&"http://example.org/ns#".to_string())
553 );
554 }
555
556 #[test]
557 fn test_empty_turtle_parsing() {
558 let parser = TurtleParser::new();
559 let result = parser.parse_str("");
560 assert!(result.is_ok());
561 assert!(result.expect("should have value").is_empty());
562 }
563
564 #[test]
565 fn test_turtle_comments() {
566 let parser = TurtleParser::new();
567 let turtle = "# This is a comment\n# Another comment";
568 let result = parser.parse_str(turtle);
569 assert!(result.is_ok());
570 assert!(result.expect("should have value").is_empty());
571 }
572}