1use crate::HashSet;
2use iri_rs::{Iri, iri};
3use jstrict::Parse;
4use rdfx::{
5 GeneralizedQuad as Quad,
6 LiteralTypeRef,
7 interpretation::{ReverseIdInterpretation, ReverseIriInterpretation, ReverseTermInterpretation},
8 vocabulary::{BlankIdVocabulary, IriVocabulary, Vocabulary},
9};
10
11enum SerTerm<I, L> {
18 Id(I),
19 Literal(L),
20}
21use std::{
22 collections::{BTreeMap, BTreeSet},
23 hash::Hash,
24 str::FromStr,
25};
26
27use crate::{
28 ExpandedDocument,
29 Id,
30 Indexed,
31 IndexedObject,
32 LangString,
33 Node,
34 Object,
35 ValidId,
36 Value,
37 object::{List, Literal},
38 rdf::{RDF_FIRST, RDF_JSON, RDF_NIL, RDF_REST, RDF_TYPE, XSD_BOOLEAN, XSD_DOUBLE, XSD_INTEGER, XSD_STRING},
39};
40
41struct SerDataset<R> {
42 named_graphs: BTreeMap<R, SerGraph<R>>,
43 default_graph: SerGraph<R>,
44}
45
46impl<R> SerDataset<R> {
47 fn new() -> Self {
48 Self {
49 named_graphs: BTreeMap::new(),
50 default_graph: SerGraph::new(),
51 }
52 }
53}
54
55impl<R: Ord> SerDataset<R> {
56 fn graph_mut(&mut self, label: Option<R>) -> &mut SerGraph<R>
57 where
58 R: Ord,
59 {
60 match label {
61 Some(g) => self.named_graphs.entry(g).or_insert_with(SerGraph::new),
62 None => &mut self.default_graph,
63 }
64 }
65
66 fn fold_into_default_graph(mut self) -> SerGraph<R> {
67 for (id, graph) in self.named_graphs {
68 self.default_graph.resource_mut(id).graph = Some(graph);
69 }
70
71 self.default_graph
72 }
73}
74
75struct SerGraph<R> {
76 resources: BTreeMap<R, SerResource<R>>,
77}
78
79struct SerList<R> {
80 first: HashSet<R>,
81 rest: HashSet<R>,
82 reverse_rest: HashSet<R>,
83 values: Option<Vec<R>>,
84}
85
86impl<R> Default for SerList<R> {
87 fn default() -> Self {
88 Self {
89 first: HashSet::default(),
90 rest: HashSet::default(),
91 reverse_rest: HashSet::default(),
92 values: None,
93 }
94 }
95}
96
97impl<R> SerList<R> {
98 fn is_well_formed(&self) -> bool {
99 self.first.len() == 1 && self.rest.len() == 1
100 }
101
102 fn is_empty(&self) -> bool {
103 self.first.is_empty() && self.rest.is_empty()
104 }
105}
106
107struct SerResource<R> {
108 types: BTreeSet<RdfType<R>>,
109 properties: BTreeMap<R, BTreeSet<R>>,
110 graph: Option<SerGraph<R>>,
111 list: SerList<R>,
112 references: usize,
113}
114
115impl<R> Default for SerResource<R> {
116 fn default() -> Self {
117 Self {
118 types: BTreeSet::new(),
119 properties: BTreeMap::new(),
120 graph: None,
121 list: SerList::default(),
122 references: 0,
123 }
124 }
125}
126
127impl<R> SerResource<R> {
128 fn is_empty(&self) -> bool {
129 self.types.is_empty() && self.properties.is_empty() && self.graph.is_none() && self.list.is_empty()
130 }
131
132 fn is_list_node(&self) -> bool {
133 self.types.iter().all(RdfType::is_list) && self.properties.is_empty() && self.graph.is_none() && self.list.is_well_formed()
134 }
135
136 fn insert(&mut self, prop: R, object: R)
137 where
138 R: Ord,
139 {
140 self.properties.entry(prop).or_default().insert(object);
141 }
142}
143
144impl<R> SerGraph<R> {
145 fn new() -> Self {
146 Self { resources: BTreeMap::new() }
147 }
148
149 fn get(&self, id: &R) -> Option<&SerResource<R>>
150 where
151 R: Ord,
152 {
153 self.resources.get(id)
154 }
155
156 fn resource_mut(&mut self, id: R) -> &mut SerResource<R>
157 where
158 R: Ord,
159 {
160 self.resources.entry(id).or_default()
161 }
162}
163
164enum RdfProperty {
165 Type,
166 First,
167 Rest,
168}
169
170fn rdf_property<V: IriVocabulary, I: ReverseIriInterpretation<Iri = V::Iri>>(vocabulary: &V, interpretation: &I, id: &I::Resource) -> Option<RdfProperty> {
171 for i in interpretation.iris_of(id) {
172 if let Some(iri) = vocabulary.iri(i) {
173 if iri == RDF_TYPE {
174 return Some(RdfProperty::Type);
175 } else if iri == RDF_FIRST {
176 return Some(RdfProperty::First);
177 } else if iri == RDF_REST {
178 return Some(RdfProperty::Rest);
179 }
180 }
181 }
182
183 None
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
187enum RdfType<R> {
188 List,
189 Other(R),
190}
191
192impl<R> RdfType<R> {
193 fn is_list(&self) -> bool {
194 matches!(self, Self::List)
195 }
196}
197
198const RDF_LIST: Iri<&'static str> = iri!("http://www.w3.org/1999/02/22-rdf-syntax-ns#List");
199
200fn rdf_type<'a, V: IriVocabulary, I: ReverseIriInterpretation<Iri = V::Iri>>(
201 vocabulary: &V,
202 interpretation: &I,
203 id: &'a I::Resource,
204) -> RdfType<&'a I::Resource> {
205 for i in interpretation.iris_of(id) {
206 if let Some(iri) = vocabulary.iri(i)
207 && iri == RDF_LIST
208 {
209 return RdfType::List;
210 }
211 }
212
213 RdfType::Other(id)
214}
215
216fn is_anonymous<I: ReverseTermInterpretation>(interpretation: &I, id: &I::Resource) -> bool {
217 interpretation.iris_of(id).next().is_none() && interpretation.literals_of(id).next().is_none()
218}
219
220#[derive(Debug, thiserror::Error)]
221pub enum SerializationError {
222 #[error("invalid JSON")]
223 InvalidJson(Box<ld_core::ContextIris>, jstrict::parse::Error),
224
225 #[error("invalid boolean value")]
226 InvalidBoolean(Box<ld_core::ContextIris>, String),
227
228 #[error("invalid number value")]
229 Number(Box<ld_core::ContextIris>, String),
230
231 #[error("literal handle does not resolve in the given vocabulary")]
232 UnresolvedLiteral,
236}
237
238#[derive(Clone, Copy)]
239pub struct RdfTerms<R> {
240 list: Option<R>,
241 first: Option<R>,
242 rest: Option<R>,
243}
244
245impl<I, B> ExpandedDocument<I, B> {
246 pub fn from_interpreted_quads_in<'a, V, T>(
253 vocabulary: &V,
254 interpretation: &T,
255 quads: impl IntoIterator<Item = Quad<&'a T::Resource, &'a T::Resource, &'a T::Resource, &'a T::Resource>>,
256 context: ld_core::Context<T>,
257 ) -> Result<Self, SerializationError>
258 where
259 V: Vocabulary<Iri = I, BlankId = B>,
260 T: ReverseTermInterpretation<Iri = I, BlankId = B, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
261 T::Resource: 'a + Ord + Hash,
262 I: Clone + Eq + Hash,
263 B: Clone + Eq + Hash,
264 {
265 let mut node_map: SerDataset<&'a T::Resource> = SerDataset::new();
266
267 let mut nil = None;
268 let mut rdf_terms = RdfTerms {
269 list: None,
270 first: None,
271 rest: None,
272 };
273
274 for quad in quads {
275 let graph = node_map.graph_mut(quad.3);
276 let subject = graph.resource_mut(quad.0);
277
278 match rdf_property(vocabulary, interpretation, quad.1) {
279 Some(RdfProperty::Type) => {
280 let ty = rdf_type(vocabulary, interpretation, quad.2);
281
282 if ty.is_list() {
283 rdf_terms.list = Some(quad.2);
284 }
285
286 subject.types.insert(ty);
287 }
288 Some(RdfProperty::First) => {
289 rdf_terms.first = Some(quad.1);
290 subject.list.first.insert(quad.2);
291 }
292 Some(RdfProperty::Rest) => {
293 rdf_terms.rest = Some(quad.1);
294 if nil.is_none() {
295 for i in ReverseIriInterpretation::iris_of(interpretation, quad.2) {
296 if let Some(iri) = vocabulary.iri(i)
297 && iri == RDF_NIL
298 {
299 nil = Some(quad.2);
300 }
301 }
302 }
303
304 subject.list.rest.insert(quad.2);
305 graph.resource_mut(quad.2).list.reverse_rest.insert(quad.0);
309 }
310 None => {
311 subject.insert(quad.1, quad.2);
312 }
313 }
314
315 let object = graph.resource_mut(quad.2);
316 if quad.1 == quad.2 {
317 object.references = usize::MAX;
318 } else {
319 let r = object.references;
320 object.references = r.saturating_add(1);
321 }
322 }
323
324 let mut graph = node_map.fold_into_default_graph();
325
326 let mut lists = Vec::new();
327 if let Some(nil_id) = nil
328 && let Some(nil) = graph.get(&nil_id)
329 {
330 for &node_id in &nil.list.reverse_rest {
331 let mut head_id = node_id;
332 if is_anonymous(interpretation, head_id)
333 && let Some(mut head) = graph.get(&head_id)
334 && head.references == 1
335 && head.is_list_node()
336 {
337 let mut values = Vec::new();
338
339 while let Some(first) = head.list.first.iter().next().copied() {
343 values.push(first);
344 let Some(parent_id) = head.list.reverse_rest.iter().next().copied() else {
345 break;
346 };
347
348 if is_anonymous(interpretation, parent_id)
349 && let Some(parent) = graph.get(&parent_id)
350 && parent.references == 1
351 && parent.is_list_node()
352 {
353 head_id = parent_id;
354 head = parent;
355 continue;
356 }
357
358 break;
359 }
360
361 values.reverse();
362 lists.push((head_id, values));
363 }
364 }
365 }
366
367 for (id, values) in lists {
368 graph.resource_mut(id).list.values = Some(values);
369 }
370
371 let mut result = ExpandedDocument::new();
372 for (id, resource) in &graph.resources {
373 if resource.references != 1 && !resource.is_empty() {
374 result.insert(render_object(vocabulary, interpretation, rdf_terms, &graph, id, resource, context)?);
375 }
376 }
377
378 Ok(result)
379 }
380
381 pub fn from_interpreted_quads<'a, V, T>(
387 vocabulary: &V,
388 interpretation: &T,
389 quads: impl IntoIterator<Item = Quad<&'a T::Resource, &'a T::Resource, &'a T::Resource, &'a T::Resource>>,
390 ) -> Result<Self, SerializationError>
391 where
392 V: Vocabulary<Iri = I, BlankId = B>,
393 T: ReverseTermInterpretation<Iri = I, BlankId = B, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
394 T::Resource: 'a + Ord + Hash,
395 I: Clone + Eq + Hash,
396 B: Clone + Eq + Hash,
397 {
398 Self::from_interpreted_quads_in(vocabulary, interpretation, quads, ld_core::Context::default())
399 }
400}
401
402fn render_object<V, I>(
403 vocabulary: &V,
404 interpretation: &I,
405 rdf_terms: RdfTerms<&I::Resource>,
406 graph: &SerGraph<&I::Resource>,
407 id: &I::Resource,
408 resource: &SerResource<&I::Resource>,
409 context: ld_core::Context<I>,
410) -> Result<IndexedObject<V::Iri, V::BlankId>, SerializationError>
411where
412 V: Vocabulary,
413 I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
414 V::Iri: Clone + Eq + Hash,
415 V::BlankId: Clone + Eq + Hash,
416 I::Resource: Ord,
417{
418 let context = context.with_subject(id);
419 if resource.is_empty() {
420 render_reference(vocabulary, interpretation, id, context)
421 } else if let Some(values) = &resource.list.values {
422 let mut objects = Vec::with_capacity(values.len());
423
424 for value in values {
425 objects.push(render_object_or_reference(vocabulary, interpretation, rdf_terms, graph, value, context)?);
426 }
427
428 Ok(Indexed::none(Object::List(List::new(objects))))
429 } else {
430 let mut node: Node<V::Iri, V::BlankId> = Node::new();
431
432 if let Some(id) = id_of(interpretation, id) {
433 node.id = Some(id);
434 }
435
436 let mut types = Vec::with_capacity(resource.types.len());
437 for ty in &resource.types {
438 let ty_resource = match ty {
439 RdfType::List => unsafe { rdf_terms.list.unwrap_unchecked() },
442 RdfType::Other(o) => o,
443 };
444
445 if let Some(ty_id) = id_of(interpretation, ty_resource) {
446 types.push(ty_id);
447 }
448 }
449
450 if !types.is_empty() {
451 node.types = Some(types);
452 }
453
454 if let Some(graph) = &resource.graph {
455 let mut value = crate::object::Graph::default();
456
457 for (id, resource) in &graph.resources {
458 if resource.references != 1 && !resource.is_empty() {
459 value.push(render_object(vocabulary, interpretation, rdf_terms, graph, id, resource, context)?);
460 }
461 }
462
463 node.graph = Some(value);
464 }
465
466 for (prop, objects) in &resource.properties {
467 insert_property(vocabulary, interpretation, rdf_terms, graph, &mut node, prop, objects.iter().copied(), context)?;
468 }
469
470 if !resource.list.first.is_empty() {
471 let rdf_first_id = unsafe { rdf_terms.first.unwrap_unchecked() };
474 insert_property(
475 vocabulary,
476 interpretation,
477 rdf_terms,
478 graph,
479 &mut node,
480 rdf_first_id,
481 resource.list.first.iter().copied(),
482 context,
483 )?;
484 }
485
486 if !resource.list.rest.is_empty() {
487 let rdf_rest_id = unsafe { rdf_terms.rest.unwrap_unchecked() };
490 insert_property(
491 vocabulary,
492 interpretation,
493 rdf_terms,
494 graph,
495 &mut node,
496 rdf_rest_id,
497 resource.list.rest.iter().copied(),
498 context,
499 )?;
500 }
501
502 Ok(Indexed::none(Object::node(node)))
503 }
504}
505
506fn insert_property<'a, V, I, O>(
507 vocabulary: &V,
508 interpretation: &I,
509 rdf_terms: RdfTerms<&'a I::Resource>,
510 graph: &SerGraph<&'a I::Resource>,
511 node: &mut Node<V::Iri, V::BlankId>,
512 prop: &I::Resource,
513 values: O,
514 context: ld_core::Context<I>,
515) -> Result<(), SerializationError>
516where
517 V: Vocabulary,
518 I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
519 V::Iri: Clone + Eq + Hash,
520 V::BlankId: Clone + Eq + Hash,
521 I::Resource: 'a + Ord,
522 O: IntoIterator<Item = &'a I::Resource>,
523 O::IntoIter: ExactSizeIterator,
524{
525 let context = context.with_predicate(prop);
526 match id_of(interpretation, prop) {
527 Some(prop) => {
528 let mut values = values.into_iter();
529
530 while values.len() > 1 {
531 let Some(value) = values.next() else { break };
533 let v = render_object_or_reference(vocabulary, interpretation, rdf_terms, graph, value, context)?;
534 node.insert(prop.clone(), v);
535 }
536
537 if let Some(value) = values.next() {
538 let v = render_object_or_reference(vocabulary, interpretation, rdf_terms, graph, value, context)?;
539 node.insert(prop, v);
540 }
541
542 Ok(())
543 }
544 None => Ok(()),
545 }
546}
547
548fn render_object_or_reference<V, I>(
549 vocabulary: &V,
550 interpretation: &I,
551 rdf_terms: RdfTerms<&I::Resource>,
552 graph: &SerGraph<&I::Resource>,
553 id: &I::Resource,
554 context: ld_core::Context<I>,
555) -> Result<IndexedObject<V::Iri, V::BlankId>, SerializationError>
556where
557 V: Vocabulary,
558 I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
559 V::Iri: Clone + Eq + Hash,
560 V::BlankId: Clone + Eq + Hash,
561 I::Resource: Ord,
562{
563 match graph.get(&id) {
564 Some(resource) => {
565 if resource.references == 1 && !resource.is_empty() {
566 render_object(vocabulary, interpretation, rdf_terms, graph, id, resource, context)
567 } else {
568 render_reference(vocabulary, interpretation, id, context)
569 }
570 }
571 None => render_reference(vocabulary, interpretation, id, context),
572 }
573}
574
575fn render_reference<V, I>(
576 vocabulary: &V,
577 interpretation: &I,
578 id: &I::Resource,
579 context: ld_core::Context<I>,
580) -> Result<IndexedObject<V::Iri, V::BlankId>, SerializationError>
581where
582 V: Vocabulary,
583 I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal>
584 + rdfx::interpretation::ReverseInterpretation
585 + rdfx::interpretation::ReverseInterpretation,
586 V::Iri: Clone,
587 V::BlankId: Clone,
588 I::Resource: Ord,
589{
590 match term_of(vocabulary, interpretation, id, context)? {
591 Some(SerTerm::Id(id)) => Ok(Indexed::none(Object::node(Node::with_id(id)))),
592 Some(SerTerm::Literal(value)) => Ok(Indexed::none(Object::Value(value))),
593 None => Ok(Indexed::none(Object::node(Node::new()))),
594 }
595}
596
597fn id_of<T>(interpretation: &T, resource: &T::Resource) -> Option<Id<T::Iri, T::BlankId>>
598where
599 T: ReverseIdInterpretation,
600 T::Iri: Clone,
601 T::BlankId: Clone,
602{
603 interpretation
604 .iris_of(resource)
605 .next()
606 .map(|i| Id::Valid(ValidId::Iri(i.clone())))
607 .or_else(|| interpretation.blank_ids_of(resource).next().map(|b| Id::Valid(ValidId::Blank(b.clone()))))
608}
609
610type ResourceTerm<V> = SerTerm<Id<<V as IriVocabulary>::Iri, <V as BlankIdVocabulary>::BlankId>, Value<<V as IriVocabulary>::Iri>>;
611
612fn term_of<V, T>(
613 vocabulary: &V,
614 interpretation: &T,
615 resource: &T::Resource,
616 context: ld_core::Context<T>,
617) -> Result<Option<ResourceTerm<V>>, SerializationError>
618where
619 V: Vocabulary,
620 T: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
621 V::Iri: Clone,
622 V::BlankId: Clone,
623{
624 match id_of(interpretation, resource) {
625 Some(id) => Ok(Some(SerTerm::Id(id))),
626 None => match rdfx::interpretation::ReverseLiteralInterpretation::literals_of(interpretation, resource).next() {
627 Some(l) => {
628 let l = vocabulary.literal(l).ok_or(SerializationError::UnresolvedLiteral)?;
631 let value = match l.type_ {
632 LiteralTypeRef::Any(d) => {
633 let ty = d.as_iri();
634 let ty_handle = vocabulary.get(ty).ok_or(SerializationError::UnresolvedLiteral)?;
638 if ty == RDF_JSON {
639 let (json, _) = jstrict::Value::parse_str(l.value)
640 .map_err(|e| SerializationError::InvalidJson(Box::new(context.into_iris(interpretation)), e))?;
641 Value::Json(json)
642 } else if ty == XSD_BOOLEAN {
643 let b = match l.as_ref() {
644 "true" | "1" => true,
645 "false" | "0" => false,
646 other => {
647 return Err(SerializationError::InvalidBoolean(
648 Box::new(context.into_iris(interpretation)),
649 other.to_owned(),
650 ));
651 }
652 };
653
654 Value::Literal(Literal::Boolean(b), Some(ty_handle))
655 } else if ty == XSD_INTEGER || ty == XSD_DOUBLE {
656 let n = jstrict::NumberBuf::from_str(l.as_str())
657 .map_err(|_| SerializationError::Number(Box::new(context.into_iris(interpretation)), l.as_ref().to_owned()))?;
658 Value::Literal(Literal::Number(n), Some(ty_handle))
659 } else if ty == XSD_STRING {
660 Value::Literal(Literal::String(l.as_ref().into()), None)
661 } else {
662 Value::Literal(Literal::String(l.as_ref().into()), Some(ty_handle))
663 }
664 }
665 LiteralTypeRef::LangString(tag) => Value::LangString(LangString::with_language(l.value.into(), tag.to_owned().into())),
666 LiteralTypeRef::DirLangString { tag, .. } => Value::LangString(LangString::with_language(l.value.into(), tag.to_owned().into())),
667 };
668
669 Ok(Some(SerTerm::Literal(value)))
670 }
671 None => Ok(None),
672 },
673 }
674}
675
676