1use super::error::SerializeResult;
7use super::error::{ParseResult, RdfParseError, TextPosition};
8use super::serializer::QuadSerializer;
9use crate::model::{BlankNode, Literal, NamedNode, Triple, TripleRef};
10use std::io::{Read, Write};
11
12#[derive(Debug, Clone)]
14enum NTriplesTerm {
15 Iri(String),
16 BlankNode(String),
17 Literal(String),
18 LanguageLiteral(String, String),
19 TypedLiteral(String, String),
20}
21
22#[derive(Debug, Clone)]
24pub struct NTriplesParser {
25 lenient: bool,
26}
27
28impl NTriplesParser {
29 pub fn new() -> Self {
31 Self { lenient: false }
32 }
33
34 pub fn lenient(mut self) -> Self {
36 self.lenient = true;
37 self
38 }
39
40 pub fn parse_reader<R: Read>(&self, mut reader: R) -> ParseResult<Vec<Triple>> {
42 let mut buffer = String::new();
44 reader.read_to_string(&mut buffer)?;
45
46 self.parse_str(&buffer)
48 }
49
50 pub fn parse_slice(&self, slice: &[u8]) -> ParseResult<Vec<Triple>> {
52 let content = std::str::from_utf8(slice)
53 .map_err(|e| RdfParseError::syntax(format!("Invalid UTF-8: {e}")))?;
54 self.parse_str(content)
55 }
56
57 pub fn parse_str(&self, input: &str) -> ParseResult<Vec<Triple>> {
59 let mut triples = Vec::new();
60 let mut line_number = 1;
61
62 for line in input.lines() {
63 let trimmed = line.trim();
64
65 if trimmed.is_empty() || trimmed.starts_with('#') {
67 line_number += 1;
68 continue;
69 }
70
71 match self.parse_triple_line(trimmed, line_number) {
73 Ok(Some(triple)) => triples.push(triple),
74 Ok(None) => {} Err(e) if self.lenient => {
76 tracing::warn!("Skipping invalid N-Triples line {line_number}: {e}");
80 }
81 Err(e) => return Err(e),
82 }
83
84 line_number += 1;
85 }
86
87 Ok(triples)
88 }
89
90 fn parse_triple_line(&self, line: &str, line_number: usize) -> ParseResult<Option<Triple>> {
92 if !line.ends_with('.') {
99 return Err(RdfParseError::syntax_at(
100 "N-Triples line must end with '.'",
101 TextPosition::new(line_number, line.len(), 0),
102 ));
103 }
104
105 let line_without_dot = line[..line.len() - 1].trim();
107
108 let mut terms = Vec::new();
110 let mut pos = 0;
111
112 let (subject_term, new_pos) = self.parse_term(line_without_dot, pos, line_number)?;
114 terms.push(subject_term);
115 pos = new_pos;
116
117 let (predicate_term, new_pos) = self.parse_term(line_without_dot, pos, line_number)?;
119 terms.push(predicate_term);
120 pos = new_pos;
121
122 let (object_term, _) = self.parse_term(line_without_dot, pos, line_number)?;
124 terms.push(object_term);
125
126 if terms.len() != 3 {
127 return Err(RdfParseError::syntax_at(
128 "N-Triples line must have exactly 3 terms",
129 TextPosition::new(line_number, 1, 0),
130 ));
131 }
132
133 let subject = self.term_to_subject(&terms[0], line_number)?;
135 let predicate = self.term_to_predicate(&terms[1], line_number)?;
136 let object = self.term_to_object(&terms[2], line_number)?;
137
138 Ok(Some(Triple::new(subject, predicate, object)))
139 }
140
141 pub fn is_lenient(&self) -> bool {
143 self.lenient
144 }
145
146 fn parse_term(
148 &self,
149 input: &str,
150 start_pos: usize,
151 line_number: usize,
152 ) -> ParseResult<(NTriplesTerm, usize)> {
153 let trimmed = input[start_pos..].trim_start();
154 let actual_start = start_pos + (input.len() - start_pos - trimmed.len());
155
156 if trimmed.is_empty() {
157 return Err(RdfParseError::syntax_at(
158 "Expected term but found end of line",
159 TextPosition::new(line_number, actual_start, 0),
160 ));
161 }
162
163 if trimmed.starts_with('<') {
164 self.parse_iri(trimmed, actual_start, line_number)
166 } else if trimmed.starts_with("_:") {
167 self.parse_blank_node(trimmed, actual_start, line_number)
169 } else if trimmed.starts_with('"') {
170 self.parse_literal(trimmed, actual_start, line_number)
172 } else {
173 Err(RdfParseError::syntax_at(
174 "Invalid term format. Expected <IRI>, _:blank, or \"literal\"",
175 TextPosition::new(line_number, actual_start, 0),
176 ))
177 }
178 }
179
180 fn parse_iri(
182 &self,
183 input: &str,
184 start_pos: usize,
185 line_number: usize,
186 ) -> ParseResult<(NTriplesTerm, usize)> {
187 if let Some(end_pos) = input.find('>') {
188 let iri = input[1..end_pos].to_string();
189 let new_pos = start_pos + end_pos + 1;
190 Ok((NTriplesTerm::Iri(iri), new_pos))
191 } else {
192 Err(RdfParseError::syntax_at(
193 "Unterminated IRI - missing '>'",
194 TextPosition::new(line_number, start_pos, 0),
195 ))
196 }
197 }
198
199 fn parse_blank_node(
201 &self,
202 input: &str,
203 start_pos: usize,
204 line_number: usize,
205 ) -> ParseResult<(NTriplesTerm, usize)> {
206 let mut end_pos = 2; for (i, c) in input[2..].char_indices() {
209 if c.is_whitespace() {
210 end_pos = 2 + i;
211 break;
212 }
213 end_pos = 2 + i + c.len_utf8();
214 }
215
216 let blank_id = input[2..end_pos].to_string();
217 if blank_id.is_empty() {
218 return Err(RdfParseError::syntax_at(
219 "Blank node ID cannot be empty",
220 TextPosition::new(line_number, start_pos, 0),
221 ));
222 }
223
224 let new_pos = start_pos + end_pos;
225 Ok((NTriplesTerm::BlankNode(blank_id), new_pos))
226 }
227
228 fn parse_literal(
230 &self,
231 input: &str,
232 start_pos: usize,
233 line_number: usize,
234 ) -> ParseResult<(NTriplesTerm, usize)> {
235 let mut end_quote = None;
237 let mut i = 1; let chars: Vec<char> = input.chars().collect();
239
240 while i < chars.len() {
241 if chars[i] == '"' {
242 let mut backslash_count = 0;
244 let mut j = i;
245 while j > 0 && chars[j - 1] == '\\' {
246 backslash_count += 1;
247 j -= 1;
248 }
249 if backslash_count % 2 == 0 {
250 end_quote = Some(i);
252 break;
253 }
254 }
255 i += 1;
256 }
257
258 let end_quote = end_quote.ok_or_else(|| {
259 RdfParseError::syntax_at(
260 "Unterminated literal - missing closing quote",
261 TextPosition::new(line_number, start_pos, 0),
262 )
263 })?;
264
265 let literal_value = self.unescape_literal(&input[1..end_quote], line_number, start_pos)?;
266 let mut pos_after_quote = start_pos + end_quote + 1;
267
268 let remaining = &input[end_quote + 1..];
270
271 if let Some(stripped) = remaining.strip_prefix('@') {
272 let mut lang_end = 1;
274 for (i, c) in stripped.char_indices() {
275 if c.is_whitespace() {
276 lang_end = 1 + i;
277 break;
278 }
279 lang_end = 1 + i + c.len_utf8();
280 }
281
282 let language = remaining[1..lang_end].to_string();
283 pos_after_quote = start_pos + end_quote + 1 + lang_end;
284 Ok((
285 NTriplesTerm::LanguageLiteral(literal_value, language),
286 pos_after_quote,
287 ))
288 } else if let Some(stripped) = remaining.strip_prefix("^^") {
289 if remaining.len() < 3 || !stripped.starts_with('<') {
291 return Err(RdfParseError::syntax_at(
292 "Invalid datatype format - expected ^^<datatype>",
293 TextPosition::new(line_number, pos_after_quote, 0),
294 ));
295 }
296
297 if let Some(datatype_end) = remaining[3..].find('>') {
298 let datatype = remaining[3..3 + datatype_end].to_string();
299 pos_after_quote = start_pos + end_quote + 1 + 3 + datatype_end + 1;
300 Ok((
301 NTriplesTerm::TypedLiteral(literal_value, datatype),
302 pos_after_quote,
303 ))
304 } else {
305 Err(RdfParseError::syntax_at(
306 "Unterminated datatype IRI - missing '>'",
307 TextPosition::new(line_number, pos_after_quote, 0),
308 ))
309 }
310 } else {
311 Ok((NTriplesTerm::Literal(literal_value), pos_after_quote))
313 }
314 }
315
316 fn unescape_literal(
318 &self,
319 value: &str,
320 line_number: usize,
321 start_pos: usize,
322 ) -> ParseResult<String> {
323 let mut result = String::new();
324 let mut chars = value.chars();
325
326 while let Some(c) = chars.next() {
327 if c == '\\' {
328 match chars.next() {
329 Some('"') => result.push('"'),
330 Some('\\') => result.push('\\'),
331 Some('n') => result.push('\n'),
332 Some('r') => result.push('\r'),
333 Some('t') => result.push('\t'),
334 Some('u') => {
335 let hex_chars: String = chars.by_ref().take(4).collect();
337 if hex_chars.len() != 4 {
338 return Err(RdfParseError::syntax_at(
339 "Invalid Unicode escape sequence \\uHHHH - expected 4 hex digits",
340 TextPosition::new(line_number, start_pos, 0),
341 ));
342 }
343 let code_point = u32::from_str_radix(&hex_chars, 16).map_err(|_| {
344 RdfParseError::syntax_at(
345 "Invalid hex digits in Unicode escape sequence",
346 TextPosition::new(line_number, start_pos, 0),
347 )
348 })?;
349 let unicode_char = char::from_u32(code_point).ok_or_else(|| {
350 RdfParseError::syntax_at(
351 "Invalid Unicode code point",
352 TextPosition::new(line_number, start_pos, 0),
353 )
354 })?;
355 result.push(unicode_char);
356 }
357 Some('U') => {
358 let hex_chars: String = chars.by_ref().take(8).collect();
360 if hex_chars.len() != 8 {
361 return Err(RdfParseError::syntax_at(
362 "Invalid Unicode escape sequence \\UHHHHHHHH - expected 8 hex digits",
363 TextPosition::new(line_number, start_pos, 0),
364 ));
365 }
366 let code_point = u32::from_str_radix(&hex_chars, 16).map_err(|_| {
367 RdfParseError::syntax_at(
368 "Invalid hex digits in Unicode escape sequence",
369 TextPosition::new(line_number, start_pos, 0),
370 )
371 })?;
372 let unicode_char = char::from_u32(code_point).ok_or_else(|| {
373 RdfParseError::syntax_at(
374 "Invalid Unicode code point",
375 TextPosition::new(line_number, start_pos, 0),
376 )
377 })?;
378 result.push(unicode_char);
379 }
380 Some(other) => {
381 return Err(RdfParseError::syntax_at(
382 format!("Invalid escape sequence \\{other}"),
383 TextPosition::new(line_number, start_pos, 0),
384 ));
385 }
386 None => {
387 return Err(RdfParseError::syntax_at(
388 "Incomplete escape sequence at end of literal",
389 TextPosition::new(line_number, start_pos, 0),
390 ));
391 }
392 }
393 } else {
394 result.push(c);
395 }
396 }
397
398 Ok(result)
399 }
400
401 fn term_to_subject(
403 &self,
404 term: &NTriplesTerm,
405 line_number: usize,
406 ) -> ParseResult<crate::model::term::Subject> {
407 match term {
408 NTriplesTerm::Iri(iri) => {
409 let named_node = NamedNode::new(iri).map_err(|e| {
410 RdfParseError::syntax_at(
411 format!("Invalid subject IRI: {e}"),
412 TextPosition::new(line_number, 0, 0),
413 )
414 })?;
415 Ok(crate::model::term::Subject::NamedNode(named_node))
416 }
417 NTriplesTerm::BlankNode(id) => {
418 let blank_node = BlankNode::new(id).map_err(|e| {
419 RdfParseError::syntax_at(
420 format!("Invalid blank node: {e}"),
421 TextPosition::new(line_number, 0, 0),
422 )
423 })?;
424 Ok(crate::model::term::Subject::BlankNode(blank_node))
425 }
426 _ => Err(RdfParseError::syntax_at(
427 "Subject must be an IRI or blank node",
428 TextPosition::new(line_number, 0, 0),
429 )),
430 }
431 }
432
433 fn term_to_predicate(
435 &self,
436 term: &NTriplesTerm,
437 line_number: usize,
438 ) -> ParseResult<crate::model::term::Predicate> {
439 match term {
440 NTriplesTerm::Iri(iri) => {
441 let named_node = NamedNode::new(iri).map_err(|e| {
442 RdfParseError::syntax_at(
443 format!("Invalid predicate IRI: {e}"),
444 TextPosition::new(line_number, 0, 0),
445 )
446 })?;
447 Ok(crate::model::term::Predicate::NamedNode(named_node))
448 }
449 _ => Err(RdfParseError::syntax_at(
450 "Predicate must be an IRI",
451 TextPosition::new(line_number, 0, 0),
452 )),
453 }
454 }
455
456 fn term_to_object(
458 &self,
459 term: &NTriplesTerm,
460 line_number: usize,
461 ) -> ParseResult<crate::model::term::Object> {
462 match term {
463 NTriplesTerm::Iri(iri) => {
464 let named_node = NamedNode::new(iri).map_err(|e| {
465 RdfParseError::syntax_at(
466 format!("Invalid object IRI: {e}"),
467 TextPosition::new(line_number, 0, 0),
468 )
469 })?;
470 Ok(crate::model::term::Object::NamedNode(named_node))
471 }
472 NTriplesTerm::BlankNode(id) => {
473 let blank_node = BlankNode::new(id).map_err(|e| {
474 RdfParseError::syntax_at(
475 format!("Invalid blank node: {e}"),
476 TextPosition::new(line_number, 0, 0),
477 )
478 })?;
479 Ok(crate::model::term::Object::BlankNode(blank_node))
480 }
481 NTriplesTerm::Literal(value) => {
482 let literal = Literal::new(value);
483 Ok(crate::model::term::Object::Literal(literal))
484 }
485 NTriplesTerm::LanguageLiteral(value, lang) => {
486 let literal = Literal::new_language_tagged_literal(value, lang).map_err(|e| {
487 RdfParseError::syntax_at(
488 format!("Invalid language tag: {e}"),
489 TextPosition::new(line_number, 0, 0),
490 )
491 })?;
492 Ok(crate::model::term::Object::Literal(literal))
493 }
494 NTriplesTerm::TypedLiteral(value, datatype_iri) => {
495 let datatype = NamedNode::new(datatype_iri).map_err(|e| {
496 RdfParseError::syntax_at(
497 format!("Invalid datatype IRI: {e}"),
498 TextPosition::new(line_number, 0, 0),
499 )
500 })?;
501 let literal = Literal::new_typed_literal(value, datatype);
502 Ok(crate::model::term::Object::Literal(literal))
503 }
504 }
505 }
506}
507
508impl Default for NTriplesParser {
509 fn default() -> Self {
510 Self::new()
511 }
512}
513
514#[derive(Debug, Clone)]
516pub struct NTriplesSerializer {
517 validate: bool,
518}
519
520impl NTriplesSerializer {
521 pub fn new() -> Self {
523 Self { validate: true }
524 }
525
526 pub fn unvalidated(mut self) -> Self {
528 self.validate = false;
529 self
530 }
531
532 pub fn for_writer<W: Write>(self, writer: W) -> WriterNTriplesSerializer<W> {
534 WriterNTriplesSerializer::new(writer, self)
535 }
536
537 pub fn serialize_to_string(&self, triples: &[Triple]) -> SerializeResult<String> {
539 let mut buffer = Vec::new();
540 {
541 let mut serializer = self.clone().for_writer(&mut buffer);
542 for triple in triples {
543 serializer.serialize_triple(triple.as_ref())?;
544 }
545 serializer.finish()?;
546 }
547 String::from_utf8(buffer)
548 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
549 }
550
551 pub fn is_validating(&self) -> bool {
553 self.validate
554 }
555}
556
557impl Default for NTriplesSerializer {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563#[allow(dead_code)]
565pub struct WriterNTriplesSerializer<W: Write> {
566 writer: W,
567 config: NTriplesSerializer,
568}
569
570impl<W: Write> WriterNTriplesSerializer<W> {
571 pub fn new(writer: W, config: NTriplesSerializer) -> Self {
573 Self { writer, config }
574 }
575
576 pub fn serialize_triple(&mut self, triple: TripleRef<'_>) -> SerializeResult<()> {
578 self.serialize_subject(triple.subject())?;
582 write!(self.writer, " ")?;
583
584 self.serialize_predicate(triple.predicate())?;
586 write!(self.writer, " ")?;
587
588 self.serialize_object(triple.object())?;
590 writeln!(self.writer, " .")?;
591
592 Ok(())
593 }
594
595 fn serialize_subject(
597 &mut self,
598 subject: crate::model::triple::SubjectRef<'_>,
599 ) -> SerializeResult<()> {
600 use crate::model::triple::SubjectRef;
601 match subject {
602 SubjectRef::NamedNode(node) => {
603 let escaped_iri = self.escape_iri(node.as_str());
604 write!(self.writer, "<{escaped_iri}>")?;
605 }
606 SubjectRef::BlankNode(node) => {
607 let node_str = node.as_str();
608 write!(self.writer, "_:{node_str}")?;
609 }
610 SubjectRef::Variable(var) => {
611 let var_str = var.as_str();
612 write!(self.writer, "?{var_str}")?;
613 }
614 SubjectRef::QuotedTriple(qt) => {
615 self.serialize_quoted_triple(qt.inner())?;
616 }
617 }
618 Ok(())
619 }
620
621 fn serialize_quoted_triple(&mut self, inner: &crate::model::Triple) -> SerializeResult<()> {
623 write!(self.writer, "<< ")?;
624 self.serialize_subject(inner.subject().into())?;
625 write!(self.writer, " ")?;
626 self.serialize_predicate(inner.predicate().into())?;
627 write!(self.writer, " ")?;
628 self.serialize_object(inner.object().into())?;
629 write!(self.writer, " >>")?;
630 Ok(())
631 }
632
633 fn serialize_predicate(
635 &mut self,
636 predicate: crate::model::triple::PredicateRef<'_>,
637 ) -> SerializeResult<()> {
638 use crate::model::triple::PredicateRef;
639 match predicate {
640 PredicateRef::NamedNode(node) => {
641 let escaped_iri = self.escape_iri(node.as_str());
642 write!(self.writer, "<{escaped_iri}>")?;
643 }
644 PredicateRef::Variable(var) => {
645 let var_str = var.as_str();
646 write!(self.writer, "?{var_str}")?;
647 }
648 }
649 Ok(())
650 }
651
652 fn serialize_object(
654 &mut self,
655 object: crate::model::triple::ObjectRef<'_>,
656 ) -> SerializeResult<()> {
657 use crate::model::triple::ObjectRef;
658 match object {
659 ObjectRef::NamedNode(node) => {
660 let escaped_iri = self.escape_iri(node.as_str());
661 write!(self.writer, "<{escaped_iri}>")?;
662 }
663 ObjectRef::BlankNode(node) => {
664 let node_str = node.as_str();
665 write!(self.writer, "_:{node_str}")?;
666 }
667 ObjectRef::Literal(literal) => {
668 self.serialize_literal(literal)?;
669 }
670 ObjectRef::Variable(var) => {
671 let var_str = var.as_str();
672 write!(self.writer, "?{var_str}")?;
673 }
674 ObjectRef::QuotedTriple(qt) => {
675 self.serialize_quoted_triple(qt.inner())?;
676 }
677 }
678 Ok(())
679 }
680
681 fn serialize_literal(&mut self, literal: &Literal) -> SerializeResult<()> {
683 let escaped_value = self.escape_literal(literal.value());
685 write!(self.writer, "\"{escaped_value}\"")?;
686
687 if let Some(language) = literal.language() {
689 write!(self.writer, "@{language}")?;
690 } else if literal.datatype().as_str() != crate::vocab::xsd::STRING.as_str() {
691 let escaped_datatype = self.escape_iri(literal.datatype().as_str());
692 write!(self.writer, "^^<{escaped_datatype}>")?;
693 }
694
695 Ok(())
696 }
697
698 fn escape_iri(&self, iri: &str) -> String {
700 iri.to_string()
703 }
704
705 fn escape_literal(&self, value: &str) -> String {
707 value
708 .chars()
709 .map(|c| match c {
710 '"' => "\\\"".to_string(),
711 '\\' => "\\\\".to_string(),
712 '\n' => "\\n".to_string(),
713 '\r' => "\\r".to_string(),
714 '\t' => "\\t".to_string(),
715 c if !('\u{0020}'..='\u{007E}').contains(&c) => {
716 if (c as u32) <= 0xFFFF {
718 format!("\\u{:04X}", c as u32)
719 } else {
720 format!("\\U{:08X}", c as u32)
721 }
722 }
723 _ => c.to_string(),
724 })
725 .collect()
726 }
727
728 pub fn finish(self) -> SerializeResult<W> {
730 Ok(self.writer)
731 }
732}
733
734impl<W: Write> QuadSerializer<W> for WriterNTriplesSerializer<W> {
735 fn serialize_quad(&mut self, quad: crate::model::QuadRef<'_>) -> SerializeResult<()> {
736 if quad.graph_name().is_default_graph() {
738 self.serialize_triple(quad.triple())
739 } else {
740 Ok(())
742 }
743 }
744
745 fn finish(self: Box<Self>) -> SerializeResult<W> {
746 Ok(self.writer)
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753
754 #[test]
755 fn test_ntriples_parser_creation() {
756 let parser = NTriplesParser::new();
757 assert!(!parser.is_lenient());
758 }
759
760 #[test]
761 fn test_ntriples_parser_lenient() {
762 let parser = NTriplesParser::new().lenient();
763 assert!(parser.is_lenient());
764 }
765
766 #[test]
767 fn test_ntriples_parser_lenient_skips_invalid_line_via_tracing() {
768 let input = "<http://example.org/s> <http://example.org/p> \"valid\" .\n\
773 this is not a valid ntriples line\n\
774 <http://example.org/s2> <http://example.org/p2> \"valid2\" .\n";
775 let parser = NTriplesParser::new().lenient();
776 let triples = parser.parse_str(input).expect("lenient parse succeeds");
777 assert_eq!(triples.len(), 2);
778
779 let strict_parser = NTriplesParser::new();
782 assert!(strict_parser.parse_str(input).is_err());
783 }
784
785 #[test]
786 fn test_ntriples_serializer_creation() {
787 let serializer = NTriplesSerializer::new();
788 assert!(serializer.is_validating());
789 }
790
791 #[test]
792 fn test_ntriples_serializer_unvalidated() {
793 let serializer = NTriplesSerializer::new().unvalidated();
794 assert!(!serializer.is_validating());
795 }
796
797 #[test]
798 fn test_empty_ntriples_parsing() {
799 let parser = NTriplesParser::new();
800 let result = parser.parse_str("");
801 assert!(result.is_ok());
802 assert!(result.expect("should have value").is_empty());
803 }
804
805 #[test]
806 fn test_ntriples_comments() {
807 let parser = NTriplesParser::new();
808 let ntriples = "# This is a comment\n# Another comment";
809 let result = parser.parse_str(ntriples);
810 assert!(result.is_ok());
811 assert!(result.expect("should have value").is_empty());
812 }
813
814 #[test]
815 fn test_ntriples_line_validation() {
816 let parser = NTriplesParser::new();
817
818 let result = parser.parse_triple_line(
820 "<http://example.org/s> <http://example.org/p> <http://example.org/o>",
821 1,
822 );
823 assert!(result.is_err());
824
825 let result = parser.parse_triple_line("<http://example.org/s> <http://example.org/p> .", 1);
827 assert!(result.is_err());
828 }
829
830 #[test]
831 fn test_ntriples_parsing() {
832 let parser = NTriplesParser::new();
833
834 let ntriples = "<http://example.org/s> <http://example.org/p> <http://example.org/o> .";
836 let result = parser.parse_str(ntriples);
837 assert!(result.is_ok());
838 let triples = result.expect("should have value");
839 assert_eq!(triples.len(), 1);
840
841 let ntriples = "_:s <http://example.org/p> \"literal\" .";
843 let result = parser.parse_str(ntriples);
844 assert!(result.is_ok());
845 let triples = result.expect("should have value");
846 assert_eq!(triples.len(), 1);
847
848 let ntriples = "<http://example.org/s> <http://example.org/p> \"hello\"@en .";
850 let result = parser.parse_str(ntriples);
851 assert!(result.is_ok());
852 let triples = result.expect("should have value");
853 assert_eq!(triples.len(), 1);
854
855 let ntriples = "<http://example.org/s> <http://example.org/p> \"42\"^^<http://www.w3.org/2001/XMLSchema#integer> .";
857 let result = parser.parse_str(ntriples);
858 assert!(result.is_ok());
859 let triples = result.expect("should have value");
860 assert_eq!(triples.len(), 1);
861 }
862
863 #[test]
864 fn test_ntriples_serialization() {
865 let serializer = NTriplesSerializer::new();
866
867 let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
869 let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
870 let object = Literal::new("test");
871 let triple = Triple::new(subject, predicate, object);
872
873 let result = serializer.serialize_to_string(&[triple]);
874 assert!(result.is_ok());
875 let output = result.expect("should have value");
876 assert!(output.contains("<http://example.org/s>"));
877 assert!(output.contains("<http://example.org/p>"));
878 assert!(output.contains("\"test\""));
879 assert!(output.ends_with(" .\n"));
880 }
881
882 #[test]
883 fn test_unicode_escape_parsing() {
884 let parser = NTriplesParser::new();
885
886 let ntriples = r#"<http://example.org/s> <http://example.org/p> "Euro: \u20AC" ."#;
888 let result = parser.parse_str(ntriples);
889 assert!(result.is_ok());
890 let triples = result.expect("should have value");
891 assert_eq!(triples.len(), 1);
892 if let crate::model::term::Object::Literal(lit) = triples[0].object() {
893 assert_eq!(lit.value(), "Euro: β¬");
894 } else {
895 panic!("Expected literal object");
896 }
897
898 let ntriples = r#"<http://example.org/s> <http://example.org/p> "Smile: \U0001F600" ."#;
900 let result = parser.parse_str(ntriples);
901 assert!(result.is_ok());
902 let triples = result.expect("should have value");
903 assert_eq!(triples.len(), 1);
904 if let crate::model::term::Object::Literal(lit) = triples[0].object() {
905 assert_eq!(lit.value(), "Smile: π");
906 } else {
907 panic!("Expected literal object");
908 }
909 }
910
911 #[test]
912 fn test_escape_sequence_parsing() {
913 let parser = NTriplesParser::new();
914
915 let ntriples = r#"<http://example.org/s> <http://example.org/p> "Line 1\nLine 2\tTabbed\rCarriage Return\\Backslash\"Quote" ."#;
917 let result = parser.parse_str(ntriples);
918 assert!(result.is_ok());
919 let triples = result.expect("should have value");
920 assert_eq!(triples.len(), 1);
921 if let crate::model::term::Object::Literal(lit) = triples[0].object() {
922 assert_eq!(
923 lit.value(),
924 "Line 1\nLine 2\tTabbed\rCarriage Return\\Backslash\"Quote"
925 );
926 } else {
927 panic!("Expected literal object");
928 }
929 }
930
931 #[test]
932 fn test_unicode_escape_serialization() {
933 let serializer = NTriplesSerializer::new();
934
935 let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
937 let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
938 let object = Literal::new("Hello δΈη π");
939 let triple = Triple::new(subject, predicate, object);
940
941 let result = serializer.serialize_to_string(&[triple]);
942 assert!(result.is_ok());
943 let output = result.expect("should have value");
944
945 assert!(output.contains("\\u4E16")); assert!(output.contains("\\u754C")); assert!(output.contains("\\U0001F30D")); }
950
951 #[test]
952 fn test_invalid_unicode_escapes() {
953 let parser = NTriplesParser::new();
954
955 let ntriples = r#"<http://example.org/s> <http://example.org/p> "Invalid: \u123" ."#;
957 let result = parser.parse_str(ntriples);
958 assert!(result.is_err());
959
960 let ntriples = r#"<http://example.org/s> <http://example.org/p> "Invalid: \U1234567" ."#;
962 let result = parser.parse_str(ntriples);
963 assert!(result.is_err());
964
965 let ntriples = r#"<http://example.org/s> <http://example.org/p> "Invalid: \uGHIJ" ."#;
967 let result = parser.parse_str(ntriples);
968 assert!(result.is_err());
969 }
970}