1use std::collections::{HashMap, HashSet, VecDeque};
67use std::fmt;
68use std::io::{BufRead, Read};
69use std::path::Path;
70
71use anybytes::{Bytes, View};
72use base64::engine::general_purpose::STANDARD as BASE64;
73use base64::Engine as _;
74use blake3::Hasher;
75use hifitime::prelude::*;
76use num_rational::Ratio;
77use winnow::error::InputError;
78use winnow::stream::Stream;
79use winnow::token::{take, take_while};
80use winnow::Parser;
81
82use crate::blob::encodings::longstring::LongString;
83use crate::blob::encodings::rawbytes::RawBytes;
84use crate::blob::{Blob, IntoBlob};
85use crate::id::{ExclusiveId, Id, ID_LEN};
86use crate::macros::entity;
87use crate::prelude::inlineencodings;
88use crate::trible::{Fragment, Trible, TribleSet};
89use crate::inline::encodings::genid::GenId;
90use crate::inline::encodings::hash::Handle;
91use crate::inline::encodings::shortstring::ShortString;
92use crate::inline::encodings::time::{i128_to_ordered_be, NsDuration, NsTAIInterval};
93use crate::inline::encodings::UnknownInline;
94use crate::inline::encodings::boolean::Boolean;
95use crate::inline::encodings::f64::F64;
96use crate::inline::{RawInline, IntoInline, TryToInline, Inline};
97
98const XSD: &str = "http://www.w3.org/2001/XMLSchema#";
99
100#[derive(Debug, Clone)]
105pub enum IngestError {
106 BnodeCycle {
112 labels: Vec<String>,
114 },
115 Io(String),
117}
118
119impl fmt::Display for IngestError {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 Self::BnodeCycle { labels } => {
123 write!(f, "blank-node cycle in input: {}", labels.join(", "))
124 }
125 Self::Io(msg) => write!(f, "i/o error reading n-triples: {msg}"),
126 }
127 }
128}
129
130impl std::error::Error for IngestError {}
131
132enum OutgoingFact {
152 Resolved { attr_id: Id, value_raw: RawInline },
154 BnodeRef {
157 attr_id: Id,
158 target_label: View<str>,
159 },
160}
161
162struct IncomingFact {
165 subject_id: Id,
166 attr_id: Id,
167 target_label: View<str>,
168}
169
170struct BnodeBuffer {
174 outgoing: HashMap<View<str>, Vec<OutgoingFact>>,
176 incoming: Vec<IncomingFact>,
178 salt: [u8; 16],
180}
181
182impl BnodeBuffer {
183 fn new() -> Self {
184 let mut salt = [0u8; 16];
189 rand::Rng::fill(&mut rand::thread_rng(), &mut salt[..]);
190 Self {
191 outgoing: HashMap::new(),
192 incoming: Vec::new(),
193 salt,
194 }
195 }
196
197 fn is_empty(&self) -> bool {
198 self.outgoing.is_empty() && self.incoming.is_empty()
199 }
200
201 fn push_outgoing(&mut self, label: View<str>, fact: OutgoingFact) {
202 self.outgoing.entry(label).or_default().push(fact);
203 }
204
205 fn push_incoming(&mut self, fact: IncomingFact) {
206 self.incoming.push(fact);
207 }
208
209 fn flush(self, facts: &mut TribleSet) -> Result<(), IngestError> {
211 if self.is_empty() {
212 return Ok(());
213 }
214
215 let mut deps: HashMap<View<str>, HashSet<View<str>>> = HashMap::new();
218 let mut all_labels: HashSet<View<str>> = HashSet::new();
219 for (label, edges) in &self.outgoing {
220 all_labels.insert(label.clone());
221 let entry = deps.entry(label.clone()).or_default();
222 for edge in edges {
223 if let OutgoingFact::BnodeRef { target_label, .. } = edge {
224 entry.insert(target_label.clone());
225 all_labels.insert(target_label.clone());
226 }
227 }
228 }
229 for inc in &self.incoming {
230 all_labels.insert(inc.target_label.clone());
231 }
232
233 let order = topo_sort(&all_labels, &deps).map_err(|labels| {
235 let mut sorted: Vec<String> = labels.iter().map(|v| v.as_ref().to_owned()).collect();
236 sorted.sort();
237 IngestError::BnodeCycle { labels: sorted }
238 })?;
239
240 let mut resolved: HashMap<View<str>, Id> = HashMap::new();
244 for label in order {
245 let id = resolve_bnode_id(&label, &self.outgoing, &resolved, &self.salt);
246 resolved.insert(label, id);
247 }
248
249 for (label, edges) in self.outgoing {
251 let subject_id = resolved[&label];
252 let e = ExclusiveId::force_ref(&subject_id);
253 for edge in edges {
254 let (attr_id, value_raw) = match edge {
255 OutgoingFact::Resolved { attr_id, value_raw } => (attr_id, value_raw),
256 OutgoingFact::BnodeRef {
257 attr_id,
258 target_label,
259 } => {
260 let target_id = resolved[&target_label];
261 let v: Inline<GenId> = target_id.to_inline();
262 (attr_id, v.raw)
263 }
264 };
265 let v: Inline<UnknownInline> = Inline::new(value_raw);
266 facts.insert(&Trible::new(e, &attr_id, &v));
267 }
268 }
269
270 for inc in self.incoming {
272 let target_id = resolved[&inc.target_label];
273 let e = ExclusiveId::force_ref(&inc.subject_id);
274 let g: Inline<GenId> = target_id.to_inline();
275 let v: Inline<UnknownInline> = Inline::new(g.raw);
276 facts.insert(&Trible::new(e, &inc.attr_id, &v));
277 }
278
279 Ok(())
280 }
281}
282
283fn resolve_bnode_id(
291 label: &View<str>,
292 outgoing: &HashMap<View<str>, Vec<OutgoingFact>>,
293 resolved: &HashMap<View<str>, Id>,
294 salt: &[u8; 16],
295) -> Id {
296 let pairs: Vec<(Id, RawInline)> = outgoing
297 .get(label)
298 .map(|edges| {
299 edges
300 .iter()
301 .map(|edge| match edge {
302 OutgoingFact::Resolved { attr_id, value_raw } => (*attr_id, *value_raw),
303 OutgoingFact::BnodeRef {
304 attr_id,
305 target_label,
306 } => {
307 let target_id = resolved
308 .get(target_label)
309 .expect("topo order resolved this target first");
310 let v: Inline<GenId> = target_id.to_inline();
311 (*attr_id, v.raw)
312 }
313 })
314 .collect()
315 })
316 .unwrap_or_default();
317
318 if pairs.is_empty() {
319 let mut hasher = Hasher::new();
321 hasher.update(salt);
322 hasher.update(label.as_ref().as_bytes());
323 let digest = hasher.finalize();
324 let mut raw = [0u8; ID_LEN];
325 raw.copy_from_slice(&digest.as_bytes()[digest.as_bytes().len() - ID_LEN..]);
326 return Id::new(raw).expect("non-nil from random salt");
327 }
328
329 let mut pairs = pairs;
330 pairs.sort_unstable();
331 let mut hasher = Hasher::new();
332 let mut last: Option<(Id, RawInline)> = None;
333 for (a, v) in &pairs {
334 if let Some((la, lv)) = last {
335 if *a == la && *v == lv {
336 continue;
337 }
338 }
339 hasher.update(&a[..]);
340 hasher.update(&v[..]);
341 last = Some((*a, *v));
342 }
343 let digest = hasher.finalize();
344 let mut raw = [0u8; ID_LEN];
345 raw.copy_from_slice(&digest.as_bytes()[digest.as_bytes().len() - ID_LEN..]);
346 Id::new(raw).expect("intrinsic id from non-empty pairs")
347}
348
349fn topo_sort(
353 nodes: &HashSet<View<str>>,
354 edges: &HashMap<View<str>, HashSet<View<str>>>,
355) -> Result<Vec<View<str>>, Vec<View<str>>> {
356 let mut in_degree: HashMap<View<str>, usize> =
357 nodes.iter().map(|n| (n.clone(), 0)).collect();
358 for dsts in edges.values() {
359 for dst in dsts {
360 *in_degree.entry(dst.clone()).or_insert(0) += 1;
361 }
362 }
363 let mut queue: VecDeque<View<str>> = in_degree
364 .iter()
365 .filter(|(_, d)| **d == 0)
366 .map(|(n, _)| n.clone())
367 .collect();
368 let mut order: Vec<View<str>> = Vec::with_capacity(nodes.len());
369 while let Some(n) = queue.pop_front() {
370 if let Some(dsts) = edges.get(&n) {
371 for dst in dsts {
372 let d = in_degree.get_mut(dst).expect("dst recorded above");
373 *d -= 1;
374 if *d == 0 {
375 queue.push_back(dst.clone());
376 }
377 }
378 }
379 order.push(n);
380 }
381 if order.len() < nodes.len() {
382 let cycle: Vec<View<str>> = nodes
383 .iter()
384 .filter(|n| in_degree.get(*n).copied().unwrap_or(0) > 0)
385 .cloned()
386 .collect();
387 Err(cycle)
388 } else {
389 Ok(order)
390 }
391}
392
393enum LiteralSuffix {
404 None,
405 Datatype(View<str>),
406 Language(View<str>),
407}
408
409fn skip_ws_and_comments(bytes: &mut Bytes) {
410 loop {
411 while matches!(bytes.peek_token(), Some(b) if matches!(b, b' ' | b'\t' | b'\n' | b'\r')) {
413 bytes.pop_front();
414 }
415 if bytes.peek_token() == Some(b'#') {
417 while let Some(b) = bytes.pop_front() {
418 if b == b'\n' {
419 break;
420 }
421 }
422 continue;
423 }
424 break;
425 }
426}
427
428fn skip_inline_ws(bytes: &mut Bytes) {
429 while matches!(bytes.peek_token(), Some(b' ') | Some(b'\t')) {
430 bytes.pop_front();
431 }
432}
433
434fn take_iri(bytes: &mut Bytes) -> Option<View<str>> {
444 if bytes.peek_token() != Some(b'<') {
445 return None;
446 }
447 bytes.pop_front();
448
449 {
451 let mut tentative = bytes.clone();
452 let mut take = take_while::<_, _, InputError<Bytes>>(0.., |b: u8| {
453 b > 0x20 && !matches!(b, b'<' | b'>' | b'"' | b'{' | b'}' | b'|' | b'^' | b'`' | b'\\')
456 });
457 if let Ok(prefix) = take.parse_next(&mut tentative) {
458 if tentative.peek_token() == Some(b'>') {
459 tentative.pop_front();
460 *bytes = tentative;
461 return prefix.view::<str>().ok();
462 }
463 }
464 }
465
466 let mut out: Vec<u8> = Vec::new();
470 while let Some(b) = bytes.peek_token() {
471 match b {
472 b'>' => {
473 bytes.pop_front();
474 return Bytes::from_source(out).view::<str>().ok();
475 }
476 b'\\' => {
477 bytes.pop_front();
478 let kind = bytes.pop_front()?;
479 let decoded = match kind {
480 b'u' => parse_uchar(bytes, 4)?,
481 b'U' => parse_uchar(bytes, 8)?,
482 _ => return None, };
484 out.extend_from_slice(&decoded);
485 }
486 0..=0x20 | b'<' | b'"' | b'{' | b'}' | b'|' | b'^' | b'`' => return None,
488 _ => {
489 out.push(b);
490 bytes.pop_front();
491 }
492 }
493 }
494 None
495}
496
497fn take_bnode(bytes: &mut Bytes) -> Option<View<str>> {
501 if bytes.peek_token() != Some(b'_') {
502 return None;
503 }
504 let mut tentative = bytes.clone();
505 let mut prefix = take::<_, _, InputError<Bytes>>(2usize);
506 let head = prefix.parse_next(&mut tentative).ok()?;
507 if head.as_ref() != b"_:" {
508 return None;
509 }
510 let mut take_label = take_while::<_, _, InputError<Bytes>>(1.., |b: u8| {
511 !matches!(b, b' ' | b'\t' | b'\n' | b'\r')
512 });
513 let label = take_label.parse_next(&mut tentative).ok()?;
514 *bytes = tentative;
515
516 let mut combined = Vec::with_capacity(2 + label.len());
522 combined.extend_from_slice(b"_:");
523 combined.extend_from_slice(label.as_ref());
524 Bytes::from_source(combined).view::<str>().ok()
525}
526
527fn take_literal(bytes: &mut Bytes) -> Option<(Bytes, LiteralSuffix)> {
532 if bytes.peek_token() != Some(b'"') {
533 return None;
534 }
535 bytes.pop_front();
536
537 {
539 let mut tentative = bytes.clone();
540 let mut take = take_while::<_, _, InputError<Bytes>>(0.., |b: u8| {
541 b != b'"' && b != b'\\' && b != b'\n' && b != b'\r'
542 });
543 if let Ok(prefix) = take.parse_next(&mut tentative) {
544 if tentative.peek_token() == Some(b'"') {
545 tentative.pop_front();
546 *bytes = tentative;
547 let suffix = parse_literal_suffix(bytes)?;
548 return Some((prefix, suffix));
549 }
550 }
551 }
552
553 let mut out: Vec<u8> = Vec::new();
555 loop {
556 let b = bytes.peek_token()?;
557 match b {
558 b'"' => {
559 bytes.pop_front();
560 let suffix = parse_literal_suffix(bytes)?;
561 return Some((Bytes::from_source(out), suffix));
562 }
563 b'\\' => {
564 bytes.pop_front();
565 let kind = bytes.pop_front()?;
566 match kind {
567 b'n' => out.push(b'\n'),
568 b't' => out.push(b'\t'),
569 b'r' => out.push(b'\r'),
570 b'b' => out.push(0x08),
571 b'f' => out.push(0x0c),
572 b'"' => out.push(b'"'),
573 b'\'' => out.push(b'\''),
574 b'\\' => out.push(b'\\'),
575 b'u' => {
576 let decoded = parse_uchar(bytes, 4)?;
577 out.extend_from_slice(&decoded);
578 }
579 b'U' => {
580 let decoded = parse_uchar(bytes, 8)?;
581 out.extend_from_slice(&decoded);
582 }
583 _ => return None,
584 }
585 }
586 b'\n' | b'\r' => return None,
587 _ => {
588 out.push(b);
589 bytes.pop_front();
590 }
591 }
592 }
593}
594
595fn parse_uchar(bytes: &mut Bytes, hex_digits: usize) -> Option<Vec<u8>> {
598 let mut grab = take::<_, _, InputError<Bytes>>(hex_digits);
599 let hex = grab.parse_next(bytes).ok()?;
600 let mut code: u32 = 0;
601 for h in hex.as_ref() {
602 code = (code << 4)
603 | match h {
604 b'0'..=b'9' => (h - b'0') as u32,
605 b'a'..=b'f' => (h - b'a' + 10) as u32,
606 b'A'..=b'F' => (h - b'A' + 10) as u32,
607 _ => return None,
608 };
609 }
610 let ch = char::from_u32(code)?;
611 let mut buf = [0u8; 4];
612 Some(ch.encode_utf8(&mut buf).as_bytes().to_vec())
613}
614
615fn parse_literal_suffix(bytes: &mut Bytes) -> Option<LiteralSuffix> {
617 match bytes.peek_token() {
618 Some(b'^') => {
619 bytes.pop_front();
621 if bytes.pop_front() != Some(b'^') {
622 return None;
623 }
624 let dt = take_iri(bytes)?;
625 Some(LiteralSuffix::Datatype(dt))
626 }
627 Some(b'@') => {
628 bytes.pop_front();
629 let mut take = take_while::<_, _, InputError<Bytes>>(1.., |b: u8| {
630 b.is_ascii_alphanumeric() || b == b'-'
631 });
632 let tag = take.parse_next(bytes).ok()?;
633 tag.view::<str>().ok().map(LiteralSuffix::Language)
634 }
635 _ => Some(LiteralSuffix::None),
636 }
637}
638
639fn parse_decimal(s: &str) -> Option<Ratio<i128>> {
642 if let Some(dot_pos) = s.find('.') {
643 let decimals = s.len() - dot_pos - 1;
644 let without_dot: String = s.chars().filter(|c| *c != '.').collect();
645 let numerator: i128 = without_dot.parse().ok()?;
646 let denominator: i128 = 10i128.checked_pow(decimals as u32)?;
647 Some(Ratio::new(numerator, denominator))
648 } else {
649 let n: i128 = s.parse().ok()?;
650 Some(Ratio::from_integer(n))
651 }
652}
653
654fn parse_year(mut s: &str) -> Option<(i32, &str)> {
667 let neg = if let Some(rest) = s.strip_prefix('-') {
668 s = rest;
669 true
670 } else {
671 false
672 };
673 let digits_end = s
674 .as_bytes()
675 .iter()
676 .position(|b| !b.is_ascii_digit())
677 .unwrap_or(s.len());
678 if digits_end < 4 {
679 return None;
680 }
681 let year_abs: i64 = s[..digits_end].parse().ok()?;
682 let year: i32 = if neg {
683 i32::try_from(-year_abs).ok()?
684 } else {
685 i32::try_from(year_abs).ok()?
686 };
687 Some((year, &s[digits_end..]))
688}
689
690fn parse_timezone_offset(s: &str) -> Option<i64> {
693 if s.is_empty() {
694 return Some(0);
695 }
696 if s == "Z" {
697 return Some(0);
698 }
699 let bytes = s.as_bytes();
700 let sign = match bytes.first()? {
701 b'+' => 1i64,
702 b'-' => -1i64,
703 _ => return None,
704 };
705 if bytes.len() != 6 || bytes[3] != b':' {
706 return None;
707 }
708 let hh: i64 = std::str::from_utf8(&bytes[1..3]).ok()?.parse().ok()?;
709 let mm: i64 = std::str::from_utf8(&bytes[4..6]).ok()?.parse().ok()?;
710 Some(sign * (hh * 3600 + mm * 60))
711}
712
713fn epoch_from_gregorian_with_offset(
716 year: i32,
717 month: u8,
718 day: u8,
719 hh: u8,
720 mm: u8,
721 ss: u8,
722 ns: u32,
723 offset_secs: i64,
724) -> Option<Epoch> {
725 let local = Epoch::maybe_from_gregorian_utc(year, month, day, hh, mm, ss, ns).ok()?;
730 Some(local - Duration::from_seconds(offset_secs as f64))
731}
732
733fn parse_xsd_datetime(s: &str) -> Option<i128> {
735 let (year, rest) = parse_year(s)?;
736 let mut chars = rest.as_bytes();
737 if chars.first() != Some(&b'-') {
738 return None;
739 }
740 let month: u8 = std::str::from_utf8(chars.get(1..3)?).ok()?.parse().ok()?;
741 if chars.get(3) != Some(&b'-') {
742 return None;
743 }
744 let day: u8 = std::str::from_utf8(chars.get(4..6)?).ok()?.parse().ok()?;
745 if chars.get(6) != Some(&b'T') {
746 return None;
747 }
748 let hh: u8 = std::str::from_utf8(chars.get(7..9)?).ok()?.parse().ok()?;
749 if chars.get(9) != Some(&b':') {
750 return None;
751 }
752 let mm: u8 = std::str::from_utf8(chars.get(10..12)?).ok()?.parse().ok()?;
753 if chars.get(12) != Some(&b':') {
754 return None;
755 }
756 let ss: u8 = std::str::from_utf8(chars.get(13..15)?).ok()?.parse().ok()?;
757 chars = &chars[15..];
758
759 let mut ns: u32 = 0;
760 if chars.first() == Some(&b'.') {
761 chars = &chars[1..];
762 let frac_end = chars
763 .iter()
764 .position(|b| !b.is_ascii_digit())
765 .unwrap_or(chars.len());
766 let frac_str = std::str::from_utf8(&chars[..frac_end]).ok()?;
768 let mut padded = String::with_capacity(9);
769 padded.push_str(frac_str);
770 while padded.len() < 9 {
771 padded.push('0');
772 }
773 ns = padded[..9].parse().ok()?;
774 chars = &chars[frac_end..];
775 }
776
777 let tz = std::str::from_utf8(chars).ok()?;
778 let offset = parse_timezone_offset(tz)?;
779 let epoch = epoch_from_gregorian_with_offset(year, month, day, hh, mm, ss, ns, offset)?;
780 Some(epoch.to_tai_duration().total_nanoseconds())
781}
782
783fn parse_xsd_date(s: &str) -> Option<(i128, i128)> {
786 let (year, rest) = parse_year(s)?;
787 let bytes = rest.as_bytes();
788 if bytes.first() != Some(&b'-') {
789 return None;
790 }
791 let month: u8 = std::str::from_utf8(bytes.get(1..3)?).ok()?.parse().ok()?;
792 if bytes.get(3) != Some(&b'-') {
793 return None;
794 }
795 let day: u8 = std::str::from_utf8(bytes.get(4..6)?).ok()?.parse().ok()?;
796 let tz = std::str::from_utf8(&bytes[6..]).ok()?;
797 let offset = parse_timezone_offset(tz)?;
798 let lower = epoch_from_gregorian_with_offset(year, month, day, 0, 0, 0, 0, offset)?
799 .to_tai_duration()
800 .total_nanoseconds();
801 let upper = lower.checked_add(86_400_000_000_000i128 - 1)?;
803 Some((lower, upper))
804}
805
806fn parse_xsd_gyear(s: &str) -> Option<(i128, i128)> {
809 let (year, rest) = parse_year(s)?;
810 let offset = parse_timezone_offset(rest)?;
811 let lower = epoch_from_gregorian_with_offset(year, 1, 1, 0, 0, 0, 0, offset)?
812 .to_tai_duration()
813 .total_nanoseconds();
814 let next_year = year.checked_add(1)?;
815 let upper_excl = epoch_from_gregorian_with_offset(next_year, 1, 1, 0, 0, 0, 0, offset)?
816 .to_tai_duration()
817 .total_nanoseconds();
818 Some((lower, upper_excl.checked_sub(1)?))
819}
820
821fn parse_xsd_gyearmonth(s: &str) -> Option<(i128, i128)> {
823 let (year, rest) = parse_year(s)?;
824 let bytes = rest.as_bytes();
825 if bytes.first() != Some(&b'-') {
826 return None;
827 }
828 let month: u8 = std::str::from_utf8(bytes.get(1..3)?).ok()?.parse().ok()?;
829 if !(1..=12).contains(&month) {
830 return None;
831 }
832 let tz = std::str::from_utf8(&bytes[3..]).ok()?;
833 let offset = parse_timezone_offset(tz)?;
834 let lower = epoch_from_gregorian_with_offset(year, month, 1, 0, 0, 0, 0, offset)?
835 .to_tai_duration()
836 .total_nanoseconds();
837 let (next_year, next_month) = if month == 12 {
838 (year.checked_add(1)?, 1u8)
839 } else {
840 (year, month + 1)
841 };
842 let upper_excl = epoch_from_gregorian_with_offset(next_year, next_month, 1, 0, 0, 0, 0, offset)?
843 .to_tai_duration()
844 .total_nanoseconds();
845 Some((lower, upper_excl.checked_sub(1)?))
846}
847
848fn parse_xsd_duration(s: &str) -> Option<i128> {
852 let mut s = s;
853 let neg = if let Some(rest) = s.strip_prefix('-') {
854 s = rest;
855 true
856 } else {
857 false
858 };
859 let mut s = s.strip_prefix('P')?;
860 let mut total_ns: i128 = 0;
861
862 let mut in_time = false;
863 while !s.is_empty() {
864 if let Some(rest) = s.strip_prefix('T') {
865 in_time = true;
866 s = rest;
867 continue;
868 }
869 let num_end = s
870 .as_bytes()
871 .iter()
872 .position(|b| !(b.is_ascii_digit() || *b == b'.'))?;
873 let num_str = &s[..num_end];
874 let unit = s.as_bytes().get(num_end).copied()?;
875 s = &s[num_end + 1..];
876 let value: f64 = num_str.parse().ok()?;
877 match (in_time, unit) {
878 (false, b'Y') | (false, b'M') => {
879 return None;
882 }
883 (false, b'D') => total_ns = total_ns.checked_add((value * 86_400e9) as i128)?,
884 (true, b'H') => total_ns = total_ns.checked_add((value * 3_600e9) as i128)?,
885 (true, b'M') => total_ns = total_ns.checked_add((value * 60e9) as i128)?,
886 (true, b'S') => total_ns = total_ns.checked_add((value * 1e9) as i128)?,
887 _ => return None,
888 }
889 }
890 Some(if neg { -total_ns } else { total_ns })
891}
892
893pub fn uri_to_id_pure(uri: &str) -> Id {
901 let handle: Inline<Handle<LongString>> =
902 uri.to_owned().to_blob().get_handle();
903 let fragment = entity! { crate::import::rdf_uri: handle };
904 fragment.root().expect("intrinsic URI entity")
905}
906
907fn record_uri(meta: &mut Fragment, uri: impl IntoBlob<LongString>) -> Id {
914 let handle: Inline<Handle<LongString>> = meta.put(uri);
915 let annotation = entity! { crate::import::rdf_uri: handle };
916 let id = annotation.root().expect("intrinsic URI entity");
917 *meta += annotation.into_facts();
918 id
919}
920
921#[derive(Debug)]
953pub struct NtImport {
954 pub facts: Fragment,
957 pub meta: Fragment,
961 pub triples: usize,
963}
964
965pub fn import_bytes(mut bytes: Bytes) -> Result<NtImport, IngestError> {
970 let mut facts = Fragment::empty();
971 let mut meta = Fragment::empty();
972 let mut bnodes = BnodeBuffer::new();
973 let mut count = 0;
974 let mut attr_cache = NTriplesAttrCache::default();
975
976 loop {
977 skip_ws_and_comments(&mut bytes);
978 if bytes.peek_token().is_none() {
979 break;
980 }
981 if parse_triple(
982 &mut facts,
983 &mut meta,
984 &mut bnodes,
985 &mut bytes,
986 &mut attr_cache,
987 ) {
988 count += 1;
989 } else {
990 while let Some(b) = bytes.pop_front() {
994 if b == b'\n' {
995 break;
996 }
997 }
998 }
999 }
1000
1001 bnodes.flush(facts.facts_mut())?;
1002 Ok(NtImport {
1003 facts,
1004 meta,
1005 triples: count,
1006 })
1007}
1008
1009pub fn import_blob(blob: Blob<LongString>) -> Result<NtImport, IngestError> {
1012 import_bytes(blob.bytes)
1013}
1014
1015pub fn ingest_ntriples(mut reader: impl BufRead) -> Result<NtImport, IngestError> {
1018 let mut buf = Vec::new();
1019 reader
1020 .read_to_end(&mut buf)
1021 .map_err(|e| IngestError::Io(e.to_string()))?;
1022 import_bytes(Bytes::from_source(buf))
1023}
1024
1025#[derive(Default)]
1043struct NTriplesAttrCache {
1044 genid: HashMap<String, Id>,
1045 longstring: HashMap<String, Id>,
1046 rawbytes: HashMap<String, Id>,
1047 i256be: HashMap<String, Id>,
1048 u256be: HashMap<String, Id>,
1049 r256be: HashMap<String, Id>,
1050 f64: HashMap<String, Id>,
1051 boolean: HashMap<String, Id>,
1052 nsduration: HashMap<String, Id>,
1053 nstai: HashMap<String, Id>,
1054}
1055
1056impl NTriplesAttrCache {
1057 fn resolve<S: crate::metadata::MetaDescribe>(
1061 map: &mut HashMap<String, Id>,
1062 meta: &mut Fragment,
1063 iri: &str,
1064 ) -> Id {
1065 if let Some(id) = map.get(iri) {
1066 return *id;
1067 }
1068 let h: Inline<Handle<LongString>> = meta.put(String::from(iri));
1069 let describe = entity! {
1070 crate::metadata::iri: h,
1071 crate::metadata::value_encoding: <S as crate::metadata::MetaDescribe>::id(),
1072 };
1073 let id = describe.root().expect("intrinsic attribute entity");
1074 *meta += describe.into_facts();
1076 map.insert(iri.to_string(), id);
1077 id
1078 }
1079
1080 fn genid(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1081 Self::resolve::<inlineencodings::GenId>(&mut self.genid, meta, iri)
1082 }
1083 fn longstring(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1084 Self::resolve::<Handle<LongString>>(&mut self.longstring, meta, iri)
1085 }
1086 fn rawbytes(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1087 Self::resolve::<Handle<RawBytes>>(&mut self.rawbytes, meta, iri)
1088 }
1089 fn i256be(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1090 Self::resolve::<inlineencodings::I256BE>(&mut self.i256be, meta, iri)
1091 }
1092 fn u256be(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1093 Self::resolve::<inlineencodings::U256BE>(&mut self.u256be, meta, iri)
1094 }
1095 fn r256be(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1096 Self::resolve::<inlineencodings::R256BE>(&mut self.r256be, meta, iri)
1097 }
1098 fn f64(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1099 Self::resolve::<inlineencodings::F64>(&mut self.f64, meta, iri)
1100 }
1101 fn boolean(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1102 Self::resolve::<inlineencodings::Boolean>(&mut self.boolean, meta, iri)
1103 }
1104 fn nsduration(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1105 Self::resolve::<NsDuration>(&mut self.nsduration, meta, iri)
1106 }
1107 fn nstai(&mut self, meta: &mut Fragment, iri: &str) -> Id {
1108 Self::resolve::<NsTAIInterval>(&mut self.nstai, meta, iri)
1109 }
1110}
1111
1112fn parse_triple(
1113 facts: &mut Fragment,
1114 meta: &mut Fragment,
1115 bnodes: &mut BnodeBuffer,
1116 bytes: &mut Bytes,
1117 attr_cache: &mut NTriplesAttrCache,
1118) -> bool {
1119 let (subject_iri, subject_label): (Option<View<str>>, Option<View<str>>) =
1121 match bytes.peek_token() {
1122 Some(b'<') => match take_iri(bytes) {
1123 Some(uri) => (Some(uri), None),
1124 None => return false,
1125 },
1126 Some(b'_') => match take_bnode(bytes) {
1127 Some(label) => (None, Some(label)),
1128 None => return false,
1129 },
1130 _ => return false,
1131 };
1132 skip_inline_ws(bytes);
1133
1134 let Some(predicate) = take_iri(bytes) else {
1135 return false;
1136 };
1137 skip_inline_ws(bytes);
1138
1139 let iri_subject_anchor: Option<Id> = subject_iri
1142 .as_ref()
1143 .map(|uri| record_uri(meta, uri.clone()));
1144
1145 let outcome = match bytes.peek_token() {
1147 Some(b'<') => {
1148 let Some(obj_uri) = take_iri(bytes) else {
1149 return false;
1150 };
1151 emit_object_iri(
1152 facts,
1153 meta,
1154 bnodes,
1155 iri_subject_anchor,
1156 subject_label,
1157 predicate.as_ref(),
1158 obj_uri,
1159 attr_cache,
1160 );
1161 true
1162 }
1163 Some(b'_') => {
1164 let Some(target_label) = take_bnode(bytes) else {
1165 return false;
1166 };
1167 let attr_id = attr_cache.genid(meta, predicate.as_ref());
1168 match (iri_subject_anchor, subject_label) {
1169 (Some(s_id), None) => {
1170 bnodes.push_incoming(IncomingFact {
1171 subject_id: s_id,
1172 attr_id,
1173 target_label,
1174 });
1175 }
1176 (None, Some(s_label)) => {
1177 bnodes.push_outgoing(
1178 s_label,
1179 OutgoingFact::BnodeRef {
1180 attr_id,
1181 target_label,
1182 },
1183 );
1184 }
1185 _ => return false,
1186 }
1187 true
1188 }
1189 Some(b'"') => {
1190 let Some((text_bytes, suffix)) = take_literal(bytes) else {
1191 return false;
1192 };
1193 let Ok(text) = text_bytes.view::<str>() else {
1194 return false;
1195 };
1196 match (iri_subject_anchor, subject_label) {
1197 (Some(s_id), None) => {
1198 let e = ExclusiveId::force_ref(&s_id);
1199 match suffix {
1200 LiteralSuffix::None => {
1201 emit_text_literal(facts, meta, e, predicate.as_ref(), text, attr_cache)
1202 }
1203 LiteralSuffix::Datatype(dt) => emit_typed_literal(
1204 facts,
1205 meta,
1206 e,
1207 predicate.as_ref(),
1208 text,
1209 dt.as_ref(),
1210 attr_cache,
1211 ),
1212 LiteralSuffix::Language(lang) => emit_lang_literal(
1213 facts,
1214 meta,
1215 e,
1216 predicate.as_ref(),
1217 lang.as_ref(),
1218 text,
1219 attr_cache,
1220 ),
1221 }
1222 }
1223 (None, Some(s_label)) => {
1224 if let Some(fact) = build_resolved_outgoing(
1225 facts,
1226 meta,
1227 predicate.as_ref(),
1228 text,
1229 suffix,
1230 attr_cache,
1231 ) {
1232 bnodes.push_outgoing(s_label, fact);
1233 }
1234 }
1235 _ => return false,
1236 }
1237 true
1238 }
1239 _ => false,
1240 };
1241
1242 if outcome {
1243 skip_inline_ws(bytes);
1244 if bytes.peek_token() != Some(b'.') {
1247 return false;
1248 }
1249 bytes.pop_front();
1250 }
1251 outcome
1252}
1253
1254fn emit_object_iri(
1255 facts: &mut Fragment,
1256 meta: &mut Fragment,
1257 bnodes: &mut BnodeBuffer,
1258 iri_subject_anchor: Option<Id>,
1259 subject_label: Option<View<str>>,
1260 predicate: &str,
1261 obj_uri: View<str>,
1262 attr_cache: &mut NTriplesAttrCache,
1263) {
1264 match (iri_subject_anchor, subject_label) {
1265 (Some(s_id), None) => {
1266 emit_uri_object(
1267 facts,
1268 meta,
1269 &ExclusiveId::force_ref(&s_id),
1270 predicate,
1271 obj_uri.as_ref(),
1272 attr_cache,
1273 );
1274 }
1275 (None, Some(s_label)) => {
1276 let attr_id = attr_cache.genid(meta, predicate);
1277 let obj_id = record_uri(meta, obj_uri);
1278 let g: Inline<GenId> = obj_id.to_inline();
1279 bnodes.push_outgoing(
1280 s_label,
1281 OutgoingFact::Resolved {
1282 attr_id,
1283 value_raw: g.raw,
1284 },
1285 );
1286 }
1287 _ => {}
1288 }
1289}
1290
1291fn build_resolved_outgoing(
1296 facts: &mut Fragment,
1297 meta: &mut Fragment,
1298 predicate: &str,
1299 text: View<str>,
1300 suffix: LiteralSuffix,
1301 attr_cache: &mut NTriplesAttrCache,
1302) -> Option<OutgoingFact> {
1303 match suffix {
1304 LiteralSuffix::None => {
1305 let attr_id = attr_cache.longstring(meta, predicate);
1306 let handle: Inline<Handle<LongString>> = facts.put(text);
1307 Some(OutgoingFact::Resolved {
1308 attr_id,
1309 value_raw: handle.raw,
1310 })
1311 }
1312 LiteralSuffix::Datatype(dt) => {
1313 let mut scratch = Fragment::empty();
1317 let scratch_id = Id::new([0xFF; ID_LEN]).expect("non-nil scratch id");
1318 let scratch_e = ExclusiveId::force_ref(&scratch_id);
1319 emit_typed_literal(
1324 &mut scratch,
1325 meta,
1326 scratch_e,
1327 predicate,
1328 text,
1329 dt.as_ref(),
1330 attr_cache,
1331 );
1332 let (scratch_facts, scratch_blobs) = scratch.into_facts_and_blobs();
1336 facts.blobs_mut().union(scratch_blobs);
1337 let pair = scratch_facts
1338 .iter()
1339 .next()
1340 .map(|t| (*t.a(), t.v::<UnknownInline>().raw));
1341 pair.map(|(attr_id, value_raw)| OutgoingFact::Resolved { attr_id, value_raw })
1342 }
1343 LiteralSuffix::Language(lang) => {
1344 let Ok(lang_value): Result<Inline<ShortString>, _> = lang.as_ref().try_to_inline() else {
1349 return None;
1350 };
1351 let text_handle: Inline<Handle<LongString>> = facts.put(text);
1352 let label_fragment = entity! {
1353 crate::import::rdf_lang: lang_value,
1354 crate::import::rdf_text: text_handle,
1355 };
1356 let label_id = label_fragment
1357 .root()
1358 .expect("intrinsic id from rdf_lang+rdf_text");
1359 *facts += label_fragment.into_facts();
1361 let attr_id = attr_cache.genid(meta, predicate);
1362 let g: Inline<GenId> = label_id.to_inline();
1363 Some(OutgoingFact::Resolved {
1364 attr_id,
1365 value_raw: g.raw,
1366 })
1367 }
1368 }
1369}
1370
1371fn emit_uri_object(
1372 facts: &mut Fragment,
1373 meta: &mut Fragment,
1374 e: &ExclusiveId,
1375 predicate: &str,
1376 obj_uri: &str,
1377 attr_cache: &mut NTriplesAttrCache,
1378) {
1379 let attr_id = attr_cache.genid(meta, predicate);
1380 let obj_id = record_uri(meta, obj_uri.to_owned());
1381 let g: Inline<GenId> = obj_id.to_inline();
1382 facts.facts_mut().insert(&Trible::new(e, &attr_id, &g));
1383}
1384
1385fn emit_text_literal(
1386 facts: &mut Fragment,
1387 meta: &mut Fragment,
1388 e: &ExclusiveId,
1389 predicate: &str,
1390 text: View<str>,
1391 attr_cache: &mut NTriplesAttrCache,
1392) {
1393 let attr_id = attr_cache.longstring(meta, predicate);
1394 let handle: Inline<Handle<LongString>> = facts.put(text);
1395 facts.facts_mut().insert(&Trible::new(e, &attr_id, &handle));
1396}
1397
1398fn emit_typed_literal(
1399 facts: &mut Fragment,
1400 meta: &mut Fragment,
1401 e: &ExclusiveId,
1402 predicate: &str,
1403 text: View<str>,
1404 datatype: &str,
1405 attr_cache: &mut NTriplesAttrCache,
1406) {
1407 if let Some(local) = datatype.strip_prefix(XSD) {
1408 match local {
1409 "integer" | "int" | "long" | "short" | "byte" | "negativeInteger"
1410 | "nonPositiveInteger" => {
1411 if let Ok(val) = text.parse::<i128>() {
1412 let attr_id = attr_cache.i256be(meta, predicate);
1413 let v: Inline<inlineencodings::I256BE> = val.to_inline();
1414 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1415 return;
1416 }
1417 }
1418 "nonNegativeInteger" | "positiveInteger" | "unsignedInt" | "unsignedLong"
1419 | "unsignedShort" | "unsignedByte" => {
1420 if let Ok(val) = text.parse::<u128>() {
1421 let attr_id = attr_cache.u256be(meta, predicate);
1422 let v: Inline<inlineencodings::U256BE> = val.to_inline();
1423 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1424 return;
1425 }
1426 }
1427 "decimal" => {
1428 if let Some(val) = parse_decimal(text.as_ref()) {
1429 let attr_id = attr_cache.r256be(meta, predicate);
1430 let v: Inline<inlineencodings::R256BE> = val.to_inline();
1431 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1432 return;
1433 }
1434 }
1435 "float" | "double" => {
1436 if let Ok(val) = text.parse::<f64>() {
1437 let attr_id = attr_cache.f64(meta, predicate);
1438 let v: Inline<F64> = val.to_inline();
1439 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1440 return;
1441 }
1442 }
1443 "boolean" => match text.as_ref() {
1444 "true" | "1" => {
1445 let attr_id = attr_cache.boolean(meta, predicate);
1446 let v: Inline<Boolean> = true.to_inline();
1447 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1448 return;
1449 }
1450 "false" | "0" => {
1451 let attr_id = attr_cache.boolean(meta, predicate);
1452 let v: Inline<Boolean> = false.to_inline();
1453 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1454 return;
1455 }
1456 _ => {}
1457 },
1458 "dateTime" => {
1459 if let Some(ns) = parse_xsd_datetime(text.as_ref()) {
1460 emit_interval(facts.facts_mut(), meta, e, predicate, ns, ns, attr_cache);
1461 return;
1462 }
1463 }
1464 "date" => {
1465 if let Some((lo, hi)) = parse_xsd_date(text.as_ref()) {
1466 emit_interval(facts.facts_mut(), meta, e, predicate, lo, hi, attr_cache);
1467 return;
1468 }
1469 }
1470 "gYear" => {
1471 if let Some((lo, hi)) = parse_xsd_gyear(text.as_ref()) {
1472 emit_interval(facts.facts_mut(), meta, e, predicate, lo, hi, attr_cache);
1473 return;
1474 }
1475 }
1476 "gYearMonth" => {
1477 if let Some((lo, hi)) = parse_xsd_gyearmonth(text.as_ref()) {
1478 emit_interval(facts.facts_mut(), meta, e, predicate, lo, hi, attr_cache);
1479 return;
1480 }
1481 }
1482 "duration" | "dayTimeDuration" => {
1483 if let Some(ns) = parse_xsd_duration(text.as_ref()) {
1484 let attr_id = attr_cache.nsduration(meta, predicate);
1485 let v: Inline<NsDuration> = ns.to_inline();
1486 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1487 return;
1488 }
1489 }
1490 "hexBinary" => {
1491 if let Ok(bytes) = hex::decode(text.as_ref()) {
1492 let attr_id = attr_cache.rawbytes(meta, predicate);
1493 let handle: Inline<Handle<RawBytes>> = facts.put(bytes);
1494 facts.facts_mut().insert(&Trible::new(e, &attr_id, &handle));
1495 return;
1496 }
1497 }
1498 "base64Binary" => {
1499 if let Ok(bytes) = BASE64.decode(text.as_ref()) {
1500 let attr_id = attr_cache.rawbytes(meta, predicate);
1501 let handle: Inline<Handle<RawBytes>> = facts.put(bytes);
1502 facts.facts_mut().insert(&Trible::new(e, &attr_id, &handle));
1503 return;
1504 }
1505 }
1506 "anyURI" => {
1507 emit_uri_object(facts, meta, e, predicate, text.as_ref(), attr_cache);
1511 return;
1512 }
1513 _ => {}
1514 }
1515 }
1516 emit_text_literal(facts, meta, e, predicate, text, attr_cache);
1518}
1519
1520fn emit_interval(
1522 facts: &mut TribleSet,
1523 meta: &mut Fragment,
1524 e: &ExclusiveId,
1525 predicate: &str,
1526 lo: i128,
1527 hi: i128,
1528 attr_cache: &mut NTriplesAttrCache,
1529) {
1530 let attr_id = attr_cache.nstai(meta, predicate);
1531 let mut raw = [0u8; 32];
1532 raw[0..16].copy_from_slice(&i128_to_ordered_be(lo));
1533 raw[16..32].copy_from_slice(&i128_to_ordered_be(hi));
1534 let v: Inline<NsTAIInterval> = Inline::new(raw);
1535 facts.insert(&Trible::new(e, &attr_id, &v));
1536}
1537
1538fn emit_lang_literal(
1539 facts: &mut Fragment,
1540 meta: &mut Fragment,
1541 e: &ExclusiveId,
1542 predicate: &str,
1543 lang: &str,
1544 text: View<str>,
1545 attr_cache: &mut NTriplesAttrCache,
1546) {
1547 let Ok(lang_value): Result<Inline<ShortString>, _> = lang.try_to_inline() else {
1551 return; };
1553 let text_handle: Inline<Handle<LongString>> = facts.put(text);
1554 let label_fragment = entity! {
1555 crate::import::rdf_lang: lang_value,
1556 crate::import::rdf_text: text_handle,
1557 };
1558 let label_id = label_fragment
1559 .root()
1560 .expect("intrinsic id from rdf_lang+rdf_text");
1561 *facts += label_fragment.into_facts();
1564 let attr_id = attr_cache.genid(meta, predicate);
1565 let v: Inline<GenId> = label_id.to_inline();
1566 facts.facts_mut().insert(&Trible::new(e, &attr_id, &v));
1567}
1568
1569pub fn ingest_ntriples_file(path: &Path) -> Result<NtImport, IngestError> {
1572 let file = std::fs::File::open(path).map_err(|e| IngestError::Io(e.to_string()))?;
1573 let mut reader = std::io::BufReader::new(file);
1574 let mut buf = Vec::new();
1575 reader
1576 .read_to_end(&mut buf)
1577 .map_err(|e| IngestError::Io(e.to_string()))?;
1578 import_bytes(Bytes::from_source(buf))
1579}
1580
1581#[cfg(test)]
1584mod tests {
1585 use super::*;
1586
1587 fn bytes_of(s: &str) -> Bytes {
1588 Bytes::from_source(s.as_bytes().to_vec())
1589 }
1590
1591 #[test]
1592 fn take_iri_consumes_brackets() {
1593 let mut input = bytes_of("<http://example.org/s> rest");
1594 let iri = take_iri(&mut input).unwrap();
1595 assert_eq!(iri.as_ref(), "http://example.org/s");
1596 let remaining: Vec<u8> = (0..)
1598 .scan(input.clone(), |b, _| b.pop_front())
1599 .collect();
1600 assert_eq!(&remaining[..5], b" rest");
1601 }
1602
1603 #[test]
1604 fn take_bnode_includes_prefix() {
1605 let mut input = bytes_of("_:bf55954f96378f65ddb1da9836e2eb87 .");
1606 let label = take_bnode(&mut input).unwrap();
1607 assert_eq!(label.as_ref(), "_:bf55954f96378f65ddb1da9836e2eb87");
1608 }
1609
1610 #[test]
1611 fn take_bnode_allows_internal_dot() {
1612 let mut input = bytes_of("_:foo.bar .");
1616 let label = take_bnode(&mut input).unwrap();
1617 assert_eq!(label.as_ref(), "_:foo.bar");
1618 }
1619
1620 #[test]
1621 fn take_literal_unescaped() {
1622 let mut input = bytes_of(r#""hello" ."#);
1623 let (text, suffix) = take_literal(&mut input).unwrap();
1624 assert_eq!(text.view::<str>().unwrap().as_ref(), "hello");
1625 assert!(matches!(suffix, LiteralSuffix::None));
1626 }
1627
1628 #[test]
1629 fn take_literal_with_datatype_suffix() {
1630 let mut input = bytes_of(r#""42"^^<http://www.w3.org/2001/XMLSchema#integer> ."#);
1631 let (text, suffix) = take_literal(&mut input).unwrap();
1632 assert_eq!(text.view::<str>().unwrap().as_ref(), "42");
1633 assert!(matches!(
1634 suffix,
1635 LiteralSuffix::Datatype(ref dt)
1636 if dt.as_ref() == "http://www.w3.org/2001/XMLSchema#integer"
1637 ));
1638 }
1639
1640 #[test]
1641 fn take_literal_with_lang_tag() {
1642 let mut input = bytes_of(r#""hello"@en ."#);
1643 let (text, suffix) = take_literal(&mut input).unwrap();
1644 assert_eq!(text.view::<str>().unwrap().as_ref(), "hello");
1645 assert!(matches!(
1646 suffix,
1647 LiteralSuffix::Language(ref tag) if tag.as_ref() == "en"
1648 ));
1649 }
1650
1651 #[test]
1652 fn take_literal_with_lang_region() {
1653 let mut input = bytes_of(r#""labor"@en-US ."#);
1654 let (text, suffix) = take_literal(&mut input).unwrap();
1655 assert_eq!(text.view::<str>().unwrap().as_ref(), "labor");
1656 assert!(matches!(
1657 suffix,
1658 LiteralSuffix::Language(ref tag) if tag.as_ref() == "en-US"
1659 ));
1660 }
1661
1662 #[test]
1663 fn take_literal_with_basic_escapes() {
1664 let mut input = bytes_of(r#""line\nbreak" ."#);
1665 let (text, _) = take_literal(&mut input).unwrap();
1666 assert_eq!(text.view::<str>().unwrap().as_ref(), "line\nbreak");
1667 }
1668
1669 #[test]
1670 fn take_literal_with_extended_echar() {
1671 let mut input = bytes_of(r#""a\bb\fc\'d" ."#);
1673 let (text, _) = take_literal(&mut input).unwrap();
1674 assert_eq!(
1675 text.view::<str>().unwrap().as_ref(),
1676 "a\u{0008}b\u{000c}c'd"
1677 );
1678 }
1679
1680 #[test]
1681 fn take_literal_with_unicode_escape_4() {
1682 let mut input = bytes_of(r#""smile ☺ here" ."#);
1683 let (text, _) = take_literal(&mut input).unwrap();
1684 assert_eq!(text.view::<str>().unwrap().as_ref(), "smile ☺ here");
1685 }
1686
1687 #[test]
1688 fn take_literal_with_unicode_escape_8() {
1689 let mut input = bytes_of(r#""grin \U0001F600 here" ."#);
1691 let (text, _) = take_literal(&mut input).unwrap();
1692 assert_eq!(text.view::<str>().unwrap().as_ref(), "grin 😀 here");
1693 }
1694
1695 #[test]
1696 fn take_iri_with_unicode_escape() {
1697 let mut input = bytes_of(r#"<http://ex/é> rest"#);
1699 let iri = take_iri(&mut input).unwrap();
1700 assert_eq!(iri.as_ref(), "http://ex/é");
1701 }
1702
1703 #[test]
1704 fn decimal_parse_helper() {
1705 let r = parse_decimal("3.14").unwrap();
1706 assert_eq!(*r.numer(), 157);
1707 assert_eq!(*r.denom(), 50);
1708
1709 let r = parse_decimal("42").unwrap();
1710 assert_eq!(*r.numer(), 42);
1711 assert_eq!(*r.denom(), 1);
1712
1713 let r = parse_decimal("-0.5").unwrap();
1714 assert_eq!(*r.numer(), -1);
1715 assert_eq!(*r.denom(), 2);
1716 }
1717
1718 #[test]
1719 fn xsd_datetime_z_and_offset() {
1720 let utc = parse_xsd_datetime("2020-01-01T12:00:00Z").unwrap();
1722 let plus5 = parse_xsd_datetime("2020-01-01T17:00:00+05:00").unwrap();
1723 assert_eq!(utc, plus5);
1724 }
1725
1726 #[test]
1727 fn xsd_datetime_with_fractional_seconds() {
1728 let no_frac = parse_xsd_datetime("2020-01-01T00:00:00Z").unwrap();
1729 let with_frac = parse_xsd_datetime("2020-01-01T00:00:00.5Z").unwrap();
1730 assert_eq!(with_frac - no_frac, 500_000_000);
1731 }
1732
1733 #[test]
1734 fn xsd_datetime_bce_year() {
1735 assert!(parse_xsd_datetime("-0500-01-01T00:00:00Z").is_some());
1738 }
1739
1740 #[test]
1741 fn xsd_date_spans_one_day() {
1742 let (lo, hi) = parse_xsd_date("2020-01-01").unwrap();
1743 assert_eq!(hi - lo, 86_400_000_000_000 - 1);
1745 }
1746
1747 #[test]
1748 fn xsd_gyear_spans_full_year() {
1749 let (lo_2020, hi_2020) = parse_xsd_gyear("2020").unwrap();
1750 let (lo_2021, _) = parse_xsd_gyear("2021").unwrap();
1751 assert_eq!(hi_2020 - lo_2020, 366 * 86_400_000_000_000 - 1);
1753 assert_eq!(hi_2020 + 1, lo_2021);
1755 }
1756
1757 #[test]
1758 fn xsd_gyearmonth_spans_one_month() {
1759 let (lo_jan, hi_jan) = parse_xsd_gyearmonth("2020-01").unwrap();
1760 assert_eq!(hi_jan - lo_jan, 31 * 86_400_000_000_000 - 1);
1762
1763 let (_, hi_feb) = parse_xsd_gyearmonth("2020-02").unwrap();
1764 let (lo_mar, _) = parse_xsd_gyearmonth("2020-03").unwrap();
1765 assert_eq!(hi_feb + 1, lo_mar);
1766 }
1767
1768 #[test]
1769 fn xsd_duration_daytime_only() {
1770 let ns = parse_xsd_duration("P1DT2H3M4.5S").unwrap();
1772 let expected = 86_400_000_000_000i128
1773 + 2 * 3_600_000_000_000
1774 + 3 * 60_000_000_000
1775 + 4_500_000_000;
1776 assert_eq!(ns, expected);
1777 }
1778
1779 #[test]
1780 fn xsd_duration_negative() {
1781 let ns = parse_xsd_duration("-PT5S").unwrap();
1782 assert_eq!(ns, -5_000_000_000);
1783 }
1784
1785 #[test]
1786 fn xsd_duration_rejects_year_month() {
1787 assert!(parse_xsd_duration("P1Y").is_none());
1789 assert!(parse_xsd_duration("P1M").is_none());
1790 assert!(parse_xsd_duration("P1Y2M").is_none());
1791 }
1792}