qualia_core_db/sparql_library/parsers/
ntriples_star.rs1use crate::lexicon::{generate_60bit_token, generate_embedded_triple_id};
2use crate::rdf_star::{RdfStarParseError, RdfStarParser};
3use crate::NQuin;
9use std::io::BufReader;
11
12#[derive(Debug, Clone, Default)]
14pub struct NTriplesLineTerms {
15 pub subject: String,
16 pub predicate: String,
17 pub object: String,
18 pub outer_predicate: String,
19 pub outer_object: String,
20}
21
22pub struct NTriplesStarParser {
24 context_hash: u64,
26 last_terms: Option<NTriplesLineTerms>,
27}
28
29impl NTriplesStarParser {
30 pub fn new(context_hash: u64) -> Self {
32 Self {
33 context_hash,
34 last_terms: None,
35 }
36 }
37
38 pub fn session_context(&self) -> u64 {
40 self.context_hash
41 }
42
43 pub fn last_line_terms(&self) -> Option<&NTriplesLineTerms> {
45 self.last_terms.as_ref()
46 }
47
48 fn parse_line(&mut self, line: &str) -> Result<ParseResult, RdfStarParseError> {
52 let line = line.trim();
53 if line.is_empty() || line.starts_with('#') {
54 return Ok(ParseResult::Comment);
55 }
56
57 if line.starts_with("<<<") {
59 self.parse_embedded_triple_line(line)
60 } else {
61 self.parse_regular_triple_line(line)
62 }
63 }
64
65 fn parse_regular_triple_line(&mut self, line: &str) -> Result<ParseResult, RdfStarParseError> {
67 let parts: Vec<&str> = line.split_whitespace().collect();
69 if parts.len() < 4 {
70 return Err(RdfStarParseError::InvalidSyntax);
71 }
72
73 let subject_str = parts[0];
74 let predicate_str = parts[1];
75 let object_str = parts[2];
76
77 let subject = subject_str.trim_start_matches('<').trim_end_matches('>');
79 let predicate = predicate_str.trim_start_matches('<').trim_end_matches('>');
80 let object = object_str.trim_start_matches('<').trim_end_matches('>');
81
82 let subject_hash = generate_60bit_token(subject.as_bytes());
83 let predicate_hash = generate_60bit_token(predicate.as_bytes());
84 let object_hash = generate_60bit_token(object.as_bytes());
85 self.last_terms = Some(NTriplesLineTerms {
86 subject: subject.to_string(),
87 predicate: predicate.to_string(),
88 object: object.to_string(),
89 ..Default::default()
90 });
91
92 Ok(ParseResult::RegularTriple {
93 subject: subject_hash,
94 predicate: predicate_hash,
95 object: object_hash,
96 })
97 }
98
99 fn parse_embedded_triple_line(&mut self, line: &str) -> Result<ParseResult, RdfStarParseError> {
101 let end_embedded = line
106 .find(">>>")
107 .ok_or(RdfStarParseError::MalformedEmbeddedTriple)?;
108
109 let embedded_part = &line[3..end_embedded]; let embedded_parts: Vec<&str> = embedded_part.split_whitespace().collect();
114 if embedded_parts.len() < 3 {
115 return Err(RdfStarParseError::MalformedEmbeddedTriple);
116 }
117
118 let subject = embedded_parts[0]
119 .trim_start_matches('<')
120 .trim_end_matches('>');
121 let predicate = embedded_parts[1]
122 .trim_start_matches('<')
123 .trim_end_matches('>');
124 let object = embedded_parts[2]
125 .trim_start_matches('<')
126 .trim_end_matches('>');
127
128 let subject_hash = generate_60bit_token(subject.as_bytes());
129 let predicate_hash = generate_60bit_token(predicate.as_bytes());
130 let object_hash = generate_60bit_token(object.as_bytes());
131
132 let virtual_id = generate_embedded_triple_id(subject_hash, predicate_hash, object_hash);
134
135 let remaining = &line[end_embedded + 3..]; let outer_parts: Vec<&str> = remaining.split_whitespace().collect();
138 if outer_parts.len() < 2 {
139 return Err(RdfStarParseError::MalformedEmbeddedTriple);
140 }
141
142 let outer_predicate = outer_parts[0].trim_start_matches('<').trim_end_matches('>');
143 let outer_object = outer_parts[1].trim_start_matches('<').trim_end_matches('>');
144
145 let outer_predicate_hash = generate_60bit_token(outer_predicate.as_bytes());
146 let outer_object_hash = generate_60bit_token(outer_object.as_bytes());
147 self.last_terms = Some(NTriplesLineTerms {
148 outer_predicate: outer_predicate.to_string(),
149 outer_object: outer_object.to_string(),
150 ..Default::default()
151 });
152
153 Ok(ParseResult::EmbeddedTriple {
154 virtual_id,
155 components: [subject_hash, predicate_hash, object_hash],
156 outer_predicate: outer_predicate_hash,
157 outer_object: outer_object_hash,
158 })
159 }
160}
161
162impl RdfStarParser for NTriplesStarParser {
163 fn parse_embedded_triple(
164 &mut self,
165 input: &[u8],
166 ) -> Result<(u64, [u64; 3]), RdfStarParseError> {
167 let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
168
169 match self.parse_line(line)? {
170 ParseResult::EmbeddedTriple {
171 virtual_id,
172 components,
173 ..
174 } => Ok((virtual_id, components)),
175 _ => Err(RdfStarParseError::MalformedEmbeddedTriple),
176 }
177 }
178
179 fn parse_triple(&mut self, input: &[u8]) -> Result<(u64, u64, u64), RdfStarParseError> {
180 let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
181
182 match self.parse_line(line)? {
183 ParseResult::RegularTriple {
184 subject,
185 predicate,
186 object,
187 ..
188 } => Ok((subject, predicate, object)),
189 _ => Err(RdfStarParseError::InvalidSyntax),
190 }
191 }
192
193 fn parse_quad(&mut self, _input: &[u8]) -> Result<(u64, u64, u64, u64), RdfStarParseError> {
194 Err(RdfStarParseError::UnsupportedFeature)
196 }
197
198 fn supports_quads(&self) -> bool {
199 false
200 }
201
202 fn supports_named_graphs(&self) -> bool {
203 false
204 }
205
206 fn format_name(&self) -> &'static str {
207 "N-Triples-Star"
208 }
209}
210
211enum ParseResult {
213 Comment,
214 RegularTriple {
215 subject: u64,
216 predicate: u64,
217 object: u64,
218 },
219 EmbeddedTriple {
220 virtual_id: u64,
221 components: [u64; 3],
222 outer_predicate: u64,
223 outer_object: u64,
224 },
225}
226
227pub fn parse_ntriples_star_into<R: std::io::Read, S: crate::sparql_library::quin_sink::QuinSink>(
229 reader: R,
230 context_hash: u64,
231 sink: &mut S,
232) -> Result<u64, Box<dyn std::error::Error>> {
233 use std::io::BufRead;
234
235 let mut parser = NTriplesStarParser::new(context_hash);
236 let mut count = 0;
237 let buf_reader = BufReader::new(reader);
238
239 for line in buf_reader.lines() {
240 let line = line?;
241 match parser.parse_line(&line)? {
242 ParseResult::Comment => continue,
243 ParseResult::RegularTriple {
244 subject,
245 predicate,
246 object,
247 ..
248 } => {
249 sink.push(NQuin {
250 subject,
251 predicate,
252 object,
253 context: context_hash,
254 metadata: 0b10 << 61,
255 parity: 0,
256 })?;
257 count += 1;
258 }
259 ParseResult::EmbeddedTriple {
260 virtual_id,
261 components,
262 outer_predicate,
263 outer_object,
264 ..
265 } => {
266 sink.push(NQuin {
267 subject: virtual_id,
268 predicate: outer_predicate,
269 object: outer_object,
270 context: context_hash,
271 metadata: 0b10 << 61,
272 parity: 0,
273 })?;
274 count += 1;
275
276 sink.push(NQuin {
277 subject: components[0],
278 predicate: components[1],
279 object: components[2],
280 context: context_hash,
281 metadata: 0b10 << 61,
282 parity: 0,
283 })?;
284 count += 1;
285 }
286 }
287 }
288
289 Ok(count)
290}
291
292pub fn parse_ntriples_star_stream<R: std::io::Read>(
294 reader: R,
295 context_hash: u64,
296 sorter: &mut crate::external_sort::ExternalSorter,
297) -> Result<u64, Box<dyn std::error::Error>> {
298 parse_ntriples_star_into(reader, context_hash, sorter)
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::rdf_star::RdfStarParser;
305
306 #[test]
307 fn test_ntriples_star_parser_creation() {
308 let parser = NTriplesStarParser::new(0);
309 assert_eq!(parser.format_name(), "N-Triples-Star");
310 assert!(!parser.supports_quads());
311 assert!(!parser.supports_named_graphs());
312 }
313
314 #[test]
315 fn test_parse_regular_triple() {
316 let mut parser = NTriplesStarParser::new(0);
317 let input =
318 b"<http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> .";
319 let result = parser.parse_triple(input);
320 assert!(result.is_ok());
321 let (s, p, o) = result.unwrap();
322 assert_ne!(s, 0);
323 assert_ne!(p, 0);
324 assert_ne!(o, 0);
325 }
326
327 #[test]
328 fn test_parse_embedded_triple() {
329 let mut parser = NTriplesStarParser::new(0);
330 let input = b"<<<http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob>>> <http://example.org/saidBy> <http://example.org/Charlie> .";
331 let result = parser.parse_embedded_triple(input);
332 assert!(result.is_ok());
333 let (virtual_id, components) = result.unwrap();
334 assert_ne!(virtual_id, 0);
335 assert_ne!(components[0], 0);
336 assert_ne!(components[1], 0);
337 assert_ne!(components[2], 0);
338 }
339}