1use crate::model::{
7 NamedNode, Object, ObjectTerm, Predicate, RdfTerm, Subject, SubjectTerm, Triple,
8};
9use crate::query::algebra::{AlgebraTriplePattern, TermPattern};
10use crate::OxirsError;
11use std::fmt;
12use std::sync::Arc;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct QuotedTriple {
17 inner: Arc<Triple>,
19}
20
21impl QuotedTriple {
22 pub fn new(triple: Triple) -> Self {
24 QuotedTriple {
25 inner: Arc::new(triple),
26 }
27 }
28
29 pub fn from_arc(triple: Arc<Triple>) -> Self {
31 QuotedTriple { inner: triple }
32 }
33
34 pub fn inner(&self) -> &Triple {
36 &self.inner
37 }
38
39 pub fn subject(&self) -> &Subject {
41 self.inner.subject()
42 }
43
44 pub fn predicate(&self) -> &Predicate {
46 self.inner.predicate()
47 }
48
49 pub fn object(&self) -> &Object {
51 self.inner.object()
52 }
53
54 pub fn as_ref(&self) -> QuotedTripleRef<'_> {
56 QuotedTripleRef { inner: &self.inner }
57 }
58}
59
60impl fmt::Display for QuotedTriple {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "<< {} >>", self.inner)
63 }
64}
65
66impl RdfTerm for QuotedTriple {
67 fn as_str(&self) -> &str {
68 "<<quoted-triple>>"
70 }
71
72 fn is_quoted_triple(&self) -> bool {
73 true
74 }
75}
76
77impl SubjectTerm for QuotedTriple {}
78impl ObjectTerm for QuotedTriple {}
79
80#[cfg(feature = "serde")]
82impl serde::Serialize for QuotedTriple {
83 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84 where
85 S: serde::Serializer,
86 {
87 self.inner.as_ref().serialize(serializer)
89 }
90}
91
92#[cfg(feature = "serde")]
93impl<'de> serde::Deserialize<'de> for QuotedTriple {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: serde::Deserializer<'de>,
97 {
98 let triple = Triple::deserialize(deserializer)?;
99 Ok(QuotedTriple::new(triple))
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
105pub struct QuotedTripleRef<'a> {
106 inner: &'a Triple,
107}
108
109impl<'a> QuotedTripleRef<'a> {
110 pub fn new(triple: &'a Triple) -> Self {
112 QuotedTripleRef { inner: triple }
113 }
114
115 pub fn inner(&self) -> &'a Triple {
117 self.inner
118 }
119
120 pub fn to_owned(&self) -> QuotedTriple {
122 QuotedTriple::new(self.inner.clone())
123 }
124}
125
126impl<'a> fmt::Display for QuotedTripleRef<'a> {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 write!(f, "<< {} >>", self.inner)
129 }
130}
131
132impl<'a> RdfTerm for QuotedTripleRef<'a> {
133 fn as_str(&self) -> &str {
134 "<<quoted-triple>>"
135 }
136
137 fn is_quoted_triple(&self) -> bool {
138 true
139 }
140}
141
142pub struct Annotation {
144 pub statement: QuotedTriple,
146 pub property: NamedNode,
148 pub value: Object,
150}
151
152impl Annotation {
153 pub fn new(statement: Triple, property: NamedNode, value: Object) -> Self {
155 Annotation {
156 statement: QuotedTriple::new(statement),
157 property,
158 value,
159 }
160 }
161
162 pub fn to_triple(&self) -> Triple {
164 Triple::new(
165 Subject::QuotedTriple(Box::new(self.statement.clone())),
166 self.property.clone(),
167 self.value.clone(),
168 )
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub enum StarPattern {
175 Triple(AlgebraTriplePattern),
177 QuotedTriple {
179 subject: Box<StarPattern>,
181 predicate: TermPattern,
183 object: Box<StarPattern>,
185 },
186 Annotation {
188 statement: Box<StarPattern>,
190 property: TermPattern,
192 value: TermPattern,
194 },
195}
196
197impl StarPattern {
198 pub fn has_variables(&self) -> bool {
200 match self {
201 StarPattern::Triple(pattern) => {
202 matches!(pattern.subject, TermPattern::Variable(_))
203 || matches!(pattern.predicate, TermPattern::Variable(_))
204 || matches!(pattern.object, TermPattern::Variable(_))
205 }
206 StarPattern::QuotedTriple {
207 subject,
208 predicate: _,
209 object,
210 } => subject.has_variables() || object.has_variables(),
211 StarPattern::Annotation {
212 statement,
213 property: _,
214 value: _,
215 } => statement.has_variables(),
216 }
217 }
218
219 pub fn variables(&self) -> Vec<crate::model::Variable> {
221 let mut vars = Vec::new();
222 self.collect_variables(&mut vars);
223 vars
224 }
225
226 fn collect_variables(&self, _vars: &mut Vec<crate::model::Variable>) {
227 match self {
228 StarPattern::Triple(pattern) => {
229 if let TermPattern::Variable(ref v) = pattern.subject {
230 _vars.push(v.clone());
231 }
232 if let TermPattern::Variable(ref v) = pattern.predicate {
233 _vars.push(v.clone());
234 }
235 if let TermPattern::Variable(ref v) = pattern.object {
236 _vars.push(v.clone());
237 }
238 }
239 StarPattern::QuotedTriple {
240 subject,
241 predicate: _,
242 object,
243 } => {
244 subject.collect_variables(_vars);
245 object.collect_variables(_vars);
246 }
247 StarPattern::Annotation {
248 statement,
249 property: _,
250 value: _,
251 } => {
252 statement.collect_variables(_vars);
253 }
254 }
255 }
256}
257
258pub mod serialization {
260 use super::*;
261
262 pub mod turtle_star {
264 use super::*;
265
266 pub fn serialize_quoted_triple(qt: &QuotedTriple) -> String {
268 format!("<< {} {} {} >>", qt.subject(), qt.predicate(), qt.object())
269 }
270
271 pub fn parse_quoted_triple(input: &str) -> Result<QuotedTriple, OxirsError> {
281 let trimmed = input.trim();
282 if !trimmed.starts_with("<<") || !trimmed.ends_with(">>") || trimmed.len() < 4 {
283 return Err(OxirsError::Parse(
284 "Invalid quoted triple syntax: expected '<< subject predicate object >>'"
285 .to_string(),
286 ));
287 }
288
289 let inner = trimmed[2..trimmed.len() - 2].trim();
290 let terms = split_star_terms(inner)?;
291 if terms.len() != 3 {
292 return Err(OxirsError::Parse(format!(
293 "Invalid quoted triple: expected exactly 3 terms (subject, predicate, object), found {}",
294 terms.len()
295 )));
296 }
297
298 let subject = parse_star_subject(terms[0])?;
299 let predicate = parse_star_predicate(terms[1])?;
300 let object = parse_star_object(terms[2])?;
301
302 Ok(QuotedTriple::new(Triple::new(subject, predicate, object)))
303 }
304
305 fn split_star_terms(input: &str) -> Result<Vec<&str>, OxirsError> {
310 let bytes = input.as_bytes();
311 let mut terms = Vec::new();
312 let mut i = 0usize;
313 let mut depth = 0i32;
314 let mut in_string = false;
315 let mut escape = false;
316 let mut term_start: Option<usize> = None;
317
318 while i < bytes.len() {
319 let c = bytes[i] as char;
320 if in_string {
321 if escape {
322 escape = false;
323 } else if c == '\\' {
324 escape = true;
325 } else if c == '"' {
326 in_string = false;
327 }
328 i += 1;
329 continue;
330 }
331
332 match c {
333 '"' => {
334 in_string = true;
335 if term_start.is_none() {
336 term_start = Some(i);
337 }
338 }
339 '<' if input[i..].starts_with("<<") => {
340 depth += 1;
341 if term_start.is_none() {
342 term_start = Some(i);
343 }
344 i += 1; }
346 '>' if input[i..].starts_with(">>") => {
347 depth -= 1;
348 if depth < 0 {
349 return Err(OxirsError::Parse(
350 "Unbalanced '>>' in quoted triple term".to_string(),
351 ));
352 }
353 i += 1; }
355 '<' => {
356 if term_start.is_none() {
357 term_start = Some(i);
358 }
359 }
360 c if c.is_whitespace() && depth == 0 => {
361 if let Some(start) = term_start.take() {
362 terms.push(input[start..i].trim());
363 }
364 }
365 _ => {
366 if term_start.is_none() {
367 term_start = Some(i);
368 }
369 }
370 }
371 i += 1;
372 }
373 if in_string {
374 return Err(OxirsError::Parse(
375 "Unterminated string literal in quoted triple".to_string(),
376 ));
377 }
378 if depth != 0 {
379 return Err(OxirsError::Parse(
380 "Unbalanced '<<'/'>>' nesting in quoted triple".to_string(),
381 ));
382 }
383 if let Some(start) = term_start {
384 terms.push(input[start..].trim());
385 }
386 Ok(terms.into_iter().filter(|t| !t.is_empty()).collect())
387 }
388
389 fn parse_star_iri(term: &str) -> Result<NamedNode, OxirsError> {
390 let inner = term
391 .strip_prefix('<')
392 .and_then(|s| s.strip_suffix('>'))
393 .ok_or_else(|| OxirsError::Parse(format!("Invalid IRI term: {term}")))?;
394 NamedNode::new(inner)
395 }
396
397 fn parse_star_literal(term: &str) -> Result<crate::model::Literal, OxirsError> {
398 use crate::model::Literal;
399
400 let bytes = term.as_bytes();
402 if bytes.first() != Some(&b'"') {
403 return Err(OxirsError::Parse(format!("Invalid literal term: {term}")));
404 }
405 let mut end = None;
406 let mut escape = false;
407 for (idx, b) in bytes.iter().enumerate().skip(1) {
408 if escape {
409 escape = false;
410 } else if *b == b'\\' {
411 escape = true;
412 } else if *b == b'"' {
413 end = Some(idx);
414 break;
415 }
416 }
417 let end =
418 end.ok_or_else(|| OxirsError::Parse(format!("Unterminated literal: {term}")))?;
419 let raw_value = &term[1..end];
420 let value = unescape_star_string(raw_value)?;
421 let suffix = &term[end + 1..];
422
423 if let Some(lang) = suffix.strip_prefix('@') {
424 Literal::new_language_tagged_literal(value, lang)
425 .map_err(|e| OxirsError::Parse(format!("Invalid language tag: {e}")))
426 } else if let Some(datatype) = suffix.strip_prefix("^^") {
427 let datatype = parse_star_iri(datatype)?;
428 Ok(Literal::new_typed(value, datatype))
429 } else if suffix.is_empty() {
430 Ok(Literal::new(value))
431 } else {
432 Err(OxirsError::Parse(format!(
433 "Invalid literal suffix in term: {term}"
434 )))
435 }
436 }
437
438 fn unescape_star_string(raw: &str) -> Result<String, OxirsError> {
439 let mut result = String::with_capacity(raw.len());
440 let mut chars = raw.chars();
441 while let Some(c) = chars.next() {
442 if c != '\\' {
443 result.push(c);
444 continue;
445 }
446 match chars.next() {
447 Some('n') => result.push('\n'),
448 Some('t') => result.push('\t'),
449 Some('r') => result.push('\r'),
450 Some('"') => result.push('"'),
451 Some('\\') => result.push('\\'),
452 Some(other) => {
453 return Err(OxirsError::Parse(format!(
454 "Unsupported escape sequence '\\{other}' in quoted triple literal"
455 )))
456 }
457 None => {
458 return Err(OxirsError::Parse(
459 "Trailing backslash in quoted triple literal".to_string(),
460 ))
461 }
462 }
463 }
464 Ok(result)
465 }
466
467 fn parse_star_subject(term: &str) -> Result<Subject, OxirsError> {
468 if term.starts_with("<<") {
469 Ok(Subject::QuotedTriple(Box::new(parse_quoted_triple(term)?)))
470 } else if let Some(id) = term.strip_prefix("_:") {
471 Ok(Subject::BlankNode(crate::model::BlankNode::new(id)?))
472 } else if term.starts_with('<') {
473 Ok(Subject::NamedNode(parse_star_iri(term)?))
474 } else {
475 Err(OxirsError::Parse(format!(
476 "Unsupported subject term in quoted triple: {term}"
477 )))
478 }
479 }
480
481 fn parse_star_predicate(term: &str) -> Result<Predicate, OxirsError> {
482 if term.starts_with('<') {
483 Ok(Predicate::NamedNode(parse_star_iri(term)?))
484 } else {
485 Err(OxirsError::Parse(format!(
486 "Unsupported predicate term in quoted triple: {term}"
487 )))
488 }
489 }
490
491 fn parse_star_object(term: &str) -> Result<Object, OxirsError> {
492 if term.starts_with("<<") {
493 Ok(Object::QuotedTriple(Box::new(parse_quoted_triple(term)?)))
494 } else if let Some(id) = term.strip_prefix("_:") {
495 Ok(Object::BlankNode(crate::model::BlankNode::new(id)?))
496 } else if term.starts_with('<') {
497 Ok(Object::NamedNode(parse_star_iri(term)?))
498 } else if term.starts_with('"') {
499 Ok(Object::Literal(parse_star_literal(term)?))
500 } else {
501 Err(OxirsError::Parse(format!(
502 "Unsupported object term in quoted triple: {term}"
503 )))
504 }
505 }
506 }
507
508 pub mod sparql_star {
510 use super::*;
511
512 pub fn format_star_pattern(pattern: &StarPattern) -> String {
514 match pattern {
515 StarPattern::Triple(pattern) => pattern.to_string(),
516 StarPattern::QuotedTriple {
517 subject,
518 predicate: _,
519 object,
520 } => {
521 format!(
522 "<< {} {} {} >>",
523 format_star_pattern(subject),
524 "PREDICATE",
525 format_star_pattern(object)
526 )
527 }
528 StarPattern::Annotation {
529 statement,
530 property: _,
531 value: _,
532 } => {
533 format!(
534 "{} {} {}",
535 format_star_pattern(statement),
536 "PROPERTY",
537 "VALUE"
538 )
539 }
540 }
541 }
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548 use crate::model::{Literal, NamedNode};
549
550 #[test]
551 fn test_quoted_triple() {
552 let subject = NamedNode::new("http://example.org/alice").expect("valid IRI");
553 let predicate = NamedNode::new("http://example.org/says").expect("valid IRI");
554 let object = Object::Literal(Literal::new("Hello"));
555
556 let triple = Triple::new(subject, predicate, object);
557 let quoted = QuotedTriple::new(triple.clone());
558
559 assert_eq!(quoted.inner(), &triple);
560 assert_eq!(
561 format!("{quoted}"),
562 "<< <http://example.org/alice> <http://example.org/says> \"Hello\" . >>"
563 );
564 }
565
566 #[test]
567 fn test_annotation() {
568 let subject = NamedNode::new("http://example.org/alice").expect("valid IRI");
569 let predicate = NamedNode::new("http://example.org/age").expect("valid IRI");
570 let object = Object::Literal(Literal::new_typed(
571 "30",
572 NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").expect("valid IRI"),
573 ));
574
575 let statement = Triple::new(subject, predicate, object);
576 let ann_property = NamedNode::new("http://example.org/confidence").expect("valid IRI");
577 let ann_value = Object::Literal(Literal::new_typed(
578 "0.9",
579 NamedNode::new("http://www.w3.org/2001/XMLSchema#double").expect("valid IRI"),
580 ));
581
582 let annotation = Annotation::new(statement, ann_property, ann_value);
583 let ann_triple = annotation.to_triple();
584
585 assert!(matches!(ann_triple.subject(), Subject::QuotedTriple(_)));
586 }
587
588 #[test]
589 fn test_parse_quoted_triple_basic() {
590 use serialization::turtle_star::parse_quoted_triple;
591
592 let parsed = parse_quoted_triple(
593 "<< <http://example.org/alice> <http://example.org/says> \"Hello\" >>",
594 )
595 .expect("valid quoted triple");
596
597 assert_eq!(
598 parsed.subject(),
599 &Subject::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"))
600 );
601 assert_eq!(
602 parsed.predicate(),
603 &Predicate::NamedNode(NamedNode::new("http://example.org/says").expect("valid IRI"))
604 );
605 assert_eq!(parsed.object(), &Object::Literal(Literal::new("Hello")));
606 }
607
608 #[test]
609 fn test_parse_quoted_triple_roundtrip_with_serialize() {
610 use serialization::turtle_star::{parse_quoted_triple, serialize_quoted_triple};
611
612 let subject = NamedNode::new("http://example.org/alice").expect("valid IRI");
613 let predicate = NamedNode::new("http://example.org/age").expect("valid IRI");
614 let object = Object::Literal(Literal::new_typed(
615 "30",
616 NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").expect("valid IRI"),
617 ));
618 let original = QuotedTriple::new(Triple::new(subject, predicate, object));
619
620 let text = serialize_quoted_triple(&original);
621 let parsed = parse_quoted_triple(&text).expect("round-trip parse");
622 assert_eq!(parsed, original);
623 }
624
625 #[test]
626 fn test_parse_quoted_triple_nested() {
627 use serialization::turtle_star::parse_quoted_triple;
628
629 let input = "<< << <http://example.org/a> <http://example.org/p> <http://example.org/b> >> <http://example.org/certainty> \"0.9\"^^<http://www.w3.org/2001/XMLSchema#double> >>";
630 let parsed = parse_quoted_triple(input).expect("valid nested quoted triple");
631 assert!(matches!(parsed.subject(), Subject::QuotedTriple(_)));
632 }
633
634 #[test]
635 fn test_parse_quoted_triple_rejects_bad_syntax() {
636 use serialization::turtle_star::parse_quoted_triple;
637
638 assert!(parse_quoted_triple("not a quoted triple").is_err());
639 assert!(
640 parse_quoted_triple("<< <http://example.org/a> <http://example.org/b> >>").is_err()
641 );
642 }
643}