1use crate::diagnostics::ParseError;
4use crate::vocab;
5use oxrdf::{Graph, NamedNode, NamedNodeRef, NamedOrBlankNode, Term};
6use oxttl::{NTriplesParser, TurtleParser};
7use std::collections::HashSet;
8use std::fs::File;
9use std::io::{BufReader, Read};
10use std::path::Path;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RdfFormat {
14 Turtle,
15 NTriples,
16}
17
18pub struct Loaded {
20 pub graph: Graph,
21 pub prefixes: Vec<(String, String)>,
22 pub base: Option<String>,
23}
24
25impl Loaded {
26 pub fn from_turtle(data: &[u8], base: Option<&str>) -> Result<Self, ParseError> {
28 Self::from_turtle_reader(data, base)
29 }
30
31 pub fn from_ntriples(data: &[u8]) -> Result<Self, ParseError> {
32 Self::from_ntriples_reader(data)
33 }
34
35 pub fn from_path(
36 path: &Path,
37 format: RdfFormat,
38 base: Option<&str>,
39 ) -> Result<Self, ParseError> {
40 let file = File::open(path)
41 .map_err(|e| ParseError(format!("failed to open {}: {e}", path.display())))?;
42 let reader = BufReader::new(file);
43 match format {
44 RdfFormat::Turtle => Self::from_turtle_reader(reader, base),
45 RdfFormat::NTriples => Self::from_ntriples_reader(reader),
46 }
47 }
48
49 fn from_turtle_reader(reader: impl Read, base: Option<&str>) -> Result<Self, ParseError> {
50 let mut parser = TurtleParser::new();
51 if let Some(b) = base {
52 parser = parser
53 .with_base_iri(b)
54 .map_err(|e| ParseError(format!("invalid base IRI: {e}")))?;
55 }
56 let mut reader = parser.for_reader(reader);
57 let mut graph = Graph::new();
58 for triple in reader.by_ref() {
59 let triple = triple.map_err(|e| ParseError(format!("turtle syntax error: {e}")))?;
60 graph.insert(&triple);
61 }
62 let prefixes = reader
63 .prefixes()
64 .map(|(p, iri)| (p.to_string(), iri.to_string()))
65 .collect();
66 let base = reader.base_iri().map(|s| s.to_string());
67 Ok(Self {
68 graph,
69 prefixes,
70 base,
71 })
72 }
73
74 fn from_ntriples_reader(reader: impl Read) -> Result<Self, ParseError> {
75 let mut graph = Graph::new();
76 for triple in NTriplesParser::new().for_reader(reader) {
77 let triple = triple.map_err(|e| ParseError(format!("N-Triples syntax error: {e}")))?;
78 graph.insert(&triple);
79 }
80 Ok(Self {
81 graph,
82 prefixes: Vec::new(),
83 base: None,
84 })
85 }
86
87 pub fn objects(&self, subject: &NamedOrBlankNode, predicate: NamedNodeRef) -> Vec<Term> {
89 self.graph
90 .objects_for_subject_predicate(subject, predicate)
91 .map(|t| t.into_owned())
92 .collect()
93 }
94
95 pub fn object(&self, subject: &NamedOrBlankNode, predicate: NamedNodeRef) -> Option<Term> {
97 self.graph
98 .object_for_subject_predicate(subject, predicate)
99 .map(|t| t.into_owned())
100 }
101
102 pub fn has_type(&self, subject: &NamedOrBlankNode, ty: NamedNodeRef) -> bool {
104 self.objects(subject, vocab::RDF_TYPE)
105 .iter()
106 .any(|t| matches!(t, Term::NamedNode(n) if n.as_ref() == ty))
107 }
108
109 pub fn is_instance_of(&self, subject: &NamedOrBlankNode, ty: NamedNodeRef) -> bool {
111 let mut pending: Vec<NamedNode> = self
112 .objects(subject, vocab::RDF_TYPE)
113 .into_iter()
114 .filter_map(|term| match term {
115 Term::NamedNode(node) => Some(node),
116 _ => None,
117 })
118 .collect();
119 let mut seen = HashSet::new();
120 while let Some(class) = pending.pop() {
121 if class.as_ref() == ty {
122 return true;
123 }
124 if !seen.insert(class.clone()) {
125 continue;
126 }
127 pending.extend(
128 self.objects(&NamedOrBlankNode::NamedNode(class), vocab::RDFS_SUBCLASSOF)
129 .into_iter()
130 .filter_map(|term| match term {
131 Term::NamedNode(node) => Some(node),
132 _ => None,
133 }),
134 );
135 }
136 false
137 }
138
139 pub fn merge_from(&mut self, other: &Loaded) {
141 for triple in other.graph.iter() {
142 self.graph.insert(triple);
143 }
144 }
145
146 pub fn read_list(&self, head: &Term) -> Vec<Term> {
148 let mut out = Vec::new();
149 let mut cursor = head.clone();
150 while let Some(node) = term_to_node(&cursor) {
151 if is_nil(&cursor) {
152 break;
153 }
154 if let Some(first) = self.object(&node, vocab::RDF_FIRST) {
155 out.push(first);
156 }
157 match self.object(&node, vocab::RDF_REST) {
158 Some(rest) => cursor = rest,
159 None => break,
160 }
161 }
162 out
163 }
164}
165
166pub fn term_to_node(term: &Term) -> Option<NamedOrBlankNode> {
168 match term {
169 Term::NamedNode(n) => Some(NamedOrBlankNode::NamedNode(n.clone())),
170 Term::BlankNode(b) => Some(NamedOrBlankNode::BlankNode(b.clone())),
171 Term::Literal(_) => None,
172 }
173}
174
175pub fn is_nil(term: &Term) -> bool {
177 matches!(term, Term::NamedNode(n) if n.as_ref() == vocab::RDF_NIL)
178}
179
180pub fn as_named(term: &Term) -> Option<NamedNode> {
182 match term {
183 Term::NamedNode(n) => Some(n.clone()),
184 _ => None,
185 }
186}