Skip to main content

jsonld_core/rdf/
triples.rs

1use super::{RDF_DIRECTION, RDF_FIRST, RDF_JSON, RDF_NIL, RDF_REST, RDF_VALUE, RdfDirection, XSD_BOOLEAN, XSD_DOUBLE, XSD_INTEGER, XSD_STRING};
2use crate::{Direction, Id, Indexed, IndexedObject, Node, Object, ValidId, object::value};
3use iri_rs::IriBuf;
4use jstrict::Print;
5use langtag::LangTagBuf;
6use rdfx::{
7    Literal,
8    LocalGenerator,
9    vocabulary::{IriVocabularyMut, Vocabulary, VocabularyMut},
10};
11use smallvec::SmallVec;
12
13/// JSON-LD to RDF triple.
14pub type Triple<T, B, L> = rdfx::GeneralizedTriple<ValidId<T, B>, ValidId<T, B>, Value<T, B, L>>;
15
16/// Build a [`rdfx::Datatype`] from a static IRI constant. The IRIs used by
17/// jsonld-core are never `rdf:langString` / `rdf:dirLangString`, so the
18/// `xsd:string` fallback is unreachable.
19fn static_datatype(iri: iri_rs::Iri<&'static str>) -> rdfx::Datatype {
20    rdfx::Datatype::new(IriBuf::from(iri)).unwrap_or_else(|_| rdfx::Datatype::xsd_string())
21}
22
23/// Build a [`rdfx::Datatype`] from a vocabulary handle.
24///
25/// Returns `None` if the handle does not resolve in `vocabulary`, or if the
26/// IRI is reserved and cannot be used as a plain datatype (`rdf:langString` /
27/// `rdf:dirLangString`).
28fn datatype_of<V: rdfx::vocabulary::IriVocabulary>(vocabulary: &V, iri: &V::Iri) -> Option<rdfx::Datatype> {
29    let buf = IriBuf::from(vocabulary.iri(iri)?);
30    rdfx::Datatype::new(buf).ok()
31}
32
33impl<T: Clone, B: Clone> Id<T, B> {
34    fn rdf_value<L>(&self) -> Option<Value<T, B, L>> {
35        match self {
36            Id::Valid(id) => Some(Value::Id(id.clone())),
37            Id::Invalid(_) => None,
38        }
39    }
40}
41
42/// Iterator over the triples of a compound literal representing a language
43/// tagged string with direction.
44pub struct CompoundLiteralTriples<T, B, L> {
45    /// Compound literal identifier.
46    id: ValidId<T, B>,
47
48    /// String value.
49    value: Option<Value<T, B, L>>,
50
51    /// Direction value.
52    direction: Option<Value<T, B, L>>,
53}
54
55impl<T: Clone, B: Clone, L: Clone> CompoundLiteralTriples<T, B, L> {
56    fn next(&mut self, vocabulary: &mut impl IriVocabularyMut<Iri = T>) -> Option<Triple<T, B, L>> {
57        if let Some(value) = self.value.take() {
58            return Some(rdfx::GeneralizedTriple(self.id.clone(), ValidId::Iri(vocabulary.insert(RDF_VALUE)), value));
59        }
60
61        if let Some(direction) = self.direction.take() {
62            return Some(rdfx::GeneralizedTriple(
63                self.id.clone(),
64                ValidId::Iri(vocabulary.insert(RDF_DIRECTION)),
65                direction,
66            ));
67        }
68
69        None
70    }
71}
72
73/// Compound literal.
74pub struct CompoundLiteral<T, B, L> {
75    value: Value<T, B, L>,
76    triples: Option<CompoundLiteralTriples<T, B, L>>,
77}
78
79impl<T: Clone> crate::object::Value<T> {
80    fn rdf_value_with<V, G: LocalGenerator>(
81        &self,
82        vocabulary: &mut V,
83        generator: &mut G,
84        rdf_direction: Option<RdfDirection>,
85    ) -> Option<CompoundLiteral<T, V::BlankId, V::Literal>>
86    where
87        V: Vocabulary<Iri = T> + VocabularyMut,
88    {
89        match self {
90            Self::Json(json) => Some(CompoundLiteral {
91                value: Value::Literal(vocabulary.insert_owned_literal(Literal::new(
92                    json.compact_print().to_string(),
93                    rdfx::LiteralType::Any(static_datatype(RDF_JSON)),
94                ))),
95                triples: None,
96            }),
97            Self::LangString(lang_string) => {
98                let (string, language, direction) = lang_string.parts();
99
100                let language = match language {
101                    // A malformed language tag has no RDF representation, so
102                    // the whole literal is dropped rather than emitted untagged.
103                    Some(language) => Some(language.as_well_formed()?.to_owned()),
104                    None => None,
105                };
106
107                match direction {
108                    Some(direction) => match rdf_direction {
109                        Some(RdfDirection::I18nDatatype) => {
110                            // `i18n` IRIs are never reserved datatypes, so the
111                            // fallback is unreachable.
112                            let ty = rdfx::Datatype::new(i18n(language.as_ref(), *direction)).unwrap_or_else(|_| rdfx::Datatype::xsd_string());
113                            Some(CompoundLiteral {
114                                value: Value::Literal(vocabulary.insert_owned_literal(Literal::new(string.to_string(), rdfx::LiteralType::Any(ty)))),
115                                triples: None,
116                            })
117                        }
118                        Some(RdfDirection::CompoundLiteral) => {
119                            // A generator yielding anything but a blank id or
120                            // an IRI has no RDF representation; the literal is
121                            // dropped like a malformed language tag.
122                            let id = crate::id::generator_next_id(vocabulary, generator).ok()?;
123                            Some(CompoundLiteral {
124                                value: Value::from_id(id),
125                                triples: None,
126                            })
127                        }
128                        None => match language {
129                            Some(tag) => Some(CompoundLiteral {
130                                value: Value::Literal(vocabulary.insert_owned_literal(Literal::new(string.to_string(), rdfx::LiteralType::LangString(tag)))),
131                                triples: None,
132                            }),
133                            None => Some(CompoundLiteral {
134                                value: Value::Literal(
135                                    vocabulary.insert_owned_literal(Literal::new(string.to_string(), rdfx::LiteralType::Any(static_datatype(XSD_STRING)))),
136                                ),
137                                triples: None,
138                            }),
139                        },
140                    },
141                    None => match language {
142                        Some(tag) => Some(CompoundLiteral {
143                            value: Value::Literal(vocabulary.insert_owned_literal(Literal::new(string.to_string(), rdfx::LiteralType::LangString(tag)))),
144                            triples: None,
145                        }),
146                        None => Some(CompoundLiteral {
147                            value: Value::Literal(
148                                vocabulary.insert_owned_literal(Literal::new(string.to_string(), rdfx::LiteralType::Any(static_datatype(XSD_STRING)))),
149                            ),
150                            triples: None,
151                        }),
152                    },
153                }
154            }
155            Self::Literal(lit, ty) => {
156                let (rdf_lit, preferred_rdf_ty) = match lit {
157                    value::Literal::Boolean(b) => {
158                        let lit = if *b { "true".to_string() } else { "false".to_string() };
159
160                        (lit, Some(static_datatype(XSD_BOOLEAN)))
161                    }
162                    value::Literal::Null => ("null".to_string(), None),
163                    value::Literal::Number(n) => {
164                        if n.is_i64() && ty.as_ref().and_then(|t| vocabulary.iri(t)).is_none_or(|i| i != XSD_DOUBLE) {
165                            (n.to_string(), Some(static_datatype(XSD_INTEGER)))
166                        } else {
167                            (pretty_dtoa::dtoa(n.as_f64_lossy(), XSD_CANONICAL_FLOAT), Some(static_datatype(XSD_DOUBLE)))
168                        }
169                    }
170                    value::Literal::String(s) => (s.to_string(), None),
171                };
172
173                let rdf_ty = match ty {
174                    // A type without RDF representation (unresolvable handle,
175                    // or a reserved datatype IRI like `rdf:langString` on a
176                    // plain typed value) drops the whole literal, like a
177                    // malformed language tag above.
178                    Some(id) => Some(datatype_of(vocabulary, id)?),
179                    None => preferred_rdf_ty,
180                };
181
182                Some(CompoundLiteral {
183                    value: Value::Literal(vocabulary.insert_owned_literal(Literal::new(
184                        rdf_lit,
185                        rdfx::LiteralType::Any(rdf_ty.unwrap_or_else(|| static_datatype(XSD_STRING))),
186                    ))),
187                    triples: None,
188                })
189            }
190        }
191    }
192}
193
194// <https://www.w3.org/TR/xmlschema11-2/#f-doubleLexmap>
195pub(crate) const XSD_CANONICAL_FLOAT: pretty_dtoa::FmtFloatConfig = pretty_dtoa::FmtFloatConfig::default().force_e_notation().capitalize_e(true);
196
197impl<T: Clone, B: Clone> Node<T, B> {
198    fn rdf_value<L>(&self) -> Option<Value<T, B, L>> {
199        self.id.as_ref().and_then(Id::rdf_value)
200    }
201}
202
203impl<T: Clone, B: Clone> Object<T, B> {
204    fn rdf_value_with<V, G: LocalGenerator>(
205        &self,
206        vocabulary: &mut V,
207        generator: &mut G,
208        rdf_direction: Option<RdfDirection>,
209    ) -> Option<CompoundValue<'_, T, B, V::Literal>>
210    where
211        V: Vocabulary<Iri = T, BlankId = B> + VocabularyMut,
212    {
213        match self {
214            Self::Value(value) => value.rdf_value_with(vocabulary, generator, rdf_direction).map(|compound_value| CompoundValue {
215                value: compound_value.value,
216                triples: compound_value.triples.map(CompoundValueTriples::literal),
217            }),
218            Self::Node(node) => node.rdf_value().map(|value| CompoundValue { value, triples: None }),
219            Self::List(list) => {
220                if list.is_empty() {
221                    Some(CompoundValue {
222                        value: Value::Id(ValidId::Iri(vocabulary.insert(RDF_NIL))),
223                        triples: None,
224                    })
225                } else {
226                    // A generator yielding anything but a blank id or an IRI
227                    // has no RDF representation; the list value is dropped.
228                    let id = crate::id::generator_next_id(vocabulary, generator).ok()?;
229                    Some(CompoundValue {
230                        value: Value::from_id(id.clone()),
231                        triples: Some(CompoundValueTriples::List(ListTriples::new(list.as_slice(), id))),
232                    })
233                }
234            }
235        }
236    }
237}
238
239/// RDF value paired with the triples needed to describe it.
240pub struct CompoundValue<'a, T, B, L> {
241    /// The value itself.
242    pub value: Value<T, B, L>,
243    /// Triples describing the value, for lists and JSON literals.
244    pub triples: Option<CompoundValueTriples<'a, T, B, L>>,
245}
246
247impl<'a, T: Clone, B: Clone> crate::quad::ObjectRef<'a, T, B> {
248    /// Converts this object into an RDF value, emitting any triples it needs
249    /// through the given vocabulary and generator.
250    pub fn rdf_value_with<V, G: LocalGenerator>(
251        &self,
252        vocabulary: &mut V,
253        generator: &mut G,
254        rdf_direction: Option<RdfDirection>,
255    ) -> Option<CompoundValue<'a, T, B, V::Literal>>
256    where
257        V: Vocabulary<Iri = T, BlankId = B> + VocabularyMut,
258    {
259        match self {
260            Self::Object(object) => object.rdf_value_with(vocabulary, generator, rdf_direction),
261            Self::Node(node) => node.rdf_value().map(|value| CompoundValue { value, triples: None }),
262            Self::Ref(r) => r.rdf_value().map(|value| CompoundValue { value, triples: None }),
263        }
264    }
265}
266
267enum ListItemTriples<'a, T, B, L> {
268    NestedList(NestedListTriples<'a, T, B>),
269    CompoundLiteral(Box<CompoundLiteralTriples<T, B, L>>),
270}
271
272struct NestedListTriples<'a, T, B> {
273    head_ref: Option<ValidId<T, B>>,
274    previous: Option<ValidId<T, B>>,
275    iter: std::slice::Iter<'a, IndexedObject<T, B>>,
276}
277
278struct ListNode<'a, 'i, T, B> {
279    id: &'i ValidId<T, B>,
280    object: &'a Indexed<Object<T, B>>,
281}
282
283impl<'a, T, B> NestedListTriples<'a, T, B> {
284    fn new(list: &'a [IndexedObject<T, B>], head_ref: ValidId<T, B>) -> Self {
285        Self {
286            head_ref: Some(head_ref),
287            previous: None,
288            iter: list.iter(),
289        }
290    }
291
292    fn previous(&self) -> Option<&ValidId<T, B>> {
293        self.previous.as_ref()
294    }
295
296    /// Pull the next object of the list.
297    ///
298    /// Uses the given generator to assign as id to the list element.
299    fn next<V: VocabularyMut<Iri = T, BlankId = B>, G: LocalGenerator>(&mut self, vocabulary: &mut V, generator: &mut G) -> Option<ListNode<'a, '_, T, B>> {
300        if let Some(next) = self.iter.next() {
301            let id = match self.head_ref.take() {
302                Some(id) => id,
303                // A generator yielding anything but a blank id or an IRI has
304                // no RDF representation; the list is truncated here.
305                None => crate::id::generator_next_id(vocabulary, generator).ok()?,
306            };
307
308            Some(ListNode {
309                object: next,
310                id: self.previous.insert(id),
311            })
312        } else {
313            None
314        }
315    }
316}
317
318/// Triples describing a compound RDF value.
319pub enum CompoundValueTriples<'a, T, B, L> {
320    /// An RDF literal.
321    Literal(Box<CompoundLiteralTriples<T, B, L>>),
322    /// A list object.
323    List(ListTriples<'a, T, B, L>),
324}
325
326impl<'a, T, B, L> CompoundValueTriples<'a, T, B, L> {
327    /// Builds the triples of a JSON literal.
328    pub fn literal(l: CompoundLiteralTriples<T, B, L>) -> Self {
329        Self::Literal(Box::new(l))
330    }
331
332    /// Binds this iterator to a vocabulary and generator.
333    pub fn with<'n, V: Vocabulary<Iri = T, BlankId = B, Literal = L>, G: LocalGenerator>(
334        self,
335        vocabulary: &'n mut V,
336        generator: G,
337        rdf_direction: Option<RdfDirection>,
338    ) -> CompoundValueTriplesWith<'a, 'n, V, G> {
339        CompoundValueTriplesWith {
340            vocabulary,
341            generator,
342            rdf_direction,
343            inner: self,
344        }
345    }
346
347    /// Produces the next triple, allocating blank node identifiers as needed.
348    pub fn next<V, G: LocalGenerator>(&mut self, vocabulary: &mut V, generator: &mut G, rdf_direction: Option<RdfDirection>) -> Option<Triple<T, B, L>>
349    where
350        T: Clone,
351        B: Clone,
352        L: Clone,
353        V: Vocabulary<Iri = T, BlankId = B, Literal = L> + VocabularyMut,
354    {
355        match self {
356            Self::Literal(l) => l.next(vocabulary),
357            Self::List(l) => l.next(vocabulary, generator, rdf_direction),
358        }
359    }
360}
361
362/// Compound value triples bound to a vocabulary and a generator.
363pub struct CompoundValueTriplesWith<'a, 'n, N: Vocabulary, G: LocalGenerator> {
364    vocabulary: &'n mut N,
365    generator: G,
366    rdf_direction: Option<RdfDirection>,
367    inner: CompoundValueTriples<'a, N::Iri, N::BlankId, N::Literal>,
368}
369
370impl<N: Vocabulary + VocabularyMut, G: LocalGenerator> Iterator for CompoundValueTriplesWith<'_, '_, N, G>
371where
372    N::Iri: Clone,
373    N::BlankId: Clone,
374    N::Literal: Clone,
375{
376    type Item = Triple<N::Iri, N::BlankId, N::Literal>;
377
378    fn next(&mut self) -> Option<Self::Item> {
379        self.inner.next(self.vocabulary, &mut self.generator, self.rdf_direction)
380    }
381}
382
383/// Iterator over the RDF quads generated from a list of JSON-LD objects.
384///
385/// If the list contains nested lists, the iterator will also emit quads for those nested lists.
386pub struct ListTriples<'a, T, B, L> {
387    stack: SmallVec<[ListItemTriples<'a, T, B, L>; 2]>,
388    pending: Option<Triple<T, B, L>>,
389}
390
391impl<'a, T, B, L> ListTriples<'a, T, B, L> {
392    /// Creates a new `ListTriples`.
393    pub fn new(list: &'a [IndexedObject<T, B>], head_ref: ValidId<T, B>) -> Self {
394        let mut stack = SmallVec::new();
395        stack.push(ListItemTriples::NestedList(NestedListTriples::new(list, head_ref)));
396
397        Self { stack, pending: None }
398    }
399
400    /// Binds this iterator to a vocabulary and generator.
401    pub fn with<'n, V: Vocabulary<Iri = T, BlankId = B, Literal = L>, G: LocalGenerator>(
402        self,
403        vocabulary: &'n mut V,
404        generator: G,
405        rdf_direction: Option<RdfDirection>,
406    ) -> ListTriplesWith<'a, 'n, V, G> {
407        ListTriplesWith {
408            vocabulary,
409            generator,
410            rdf_direction,
411            inner: self,
412        }
413    }
414
415    /// Produces the next triple of the list, allocating blank node identifiers
416    /// as needed.
417    pub fn next<V, G: LocalGenerator>(&mut self, vocabulary: &mut V, generator: &mut G, rdf_direction: Option<RdfDirection>) -> Option<Triple<T, B, L>>
418    where
419        T: Clone,
420        B: Clone,
421        L: Clone,
422        V: Vocabulary<Iri = T, BlankId = B, Literal = L> + VocabularyMut,
423    {
424        loop {
425            if let Some(pending) = self.pending.take() {
426                break Some(pending);
427            }
428
429            match self.stack.last_mut() {
430                Some(ListItemTriples::CompoundLiteral(lit)) => match lit.next(vocabulary) {
431                    Some(triple) => break Some(triple),
432                    None => {
433                        self.stack.pop();
434                    }
435                },
436                Some(ListItemTriples::NestedList(list)) => {
437                    let previous = list.previous().cloned();
438                    if let Some(node) = list.next(vocabulary, generator) {
439                        if let Some(compound_value) = node.object.rdf_value_with(vocabulary, generator, rdf_direction) {
440                            let id = node.id.clone();
441
442                            if let Some(compound_triples) = compound_value.triples {
443                                match compound_triples {
444                                    CompoundValueTriples::List(nested) => self.stack.extend(nested.stack),
445                                    CompoundValueTriples::Literal(literal) => self.stack.push(ListItemTriples::CompoundLiteral(literal)),
446                                }
447                            }
448
449                            self.pending = Some(rdfx::GeneralizedTriple(
450                                id.clone(),
451                                ValidId::Iri(vocabulary.insert(RDF_FIRST)),
452                                compound_value.value,
453                            ));
454
455                            if let Some(previous_id) = previous {
456                                break Some(rdfx::GeneralizedTriple(
457                                    previous_id,
458                                    ValidId::Iri(vocabulary.insert(RDF_REST)),
459                                    Value::from_id(id),
460                                ));
461                            }
462                        }
463                    } else {
464                        self.stack.pop();
465                        if let Some(previous_id) = previous {
466                            break Some(rdfx::GeneralizedTriple(
467                                previous_id,
468                                ValidId::Iri(vocabulary.insert(RDF_REST)),
469                                Value::Id(ValidId::Iri(vocabulary.insert(RDF_NIL))),
470                            ));
471                        }
472                    }
473                }
474                None => break None,
475            }
476        }
477    }
478}
479
480/// List triples bound to a vocabulary and a generator.
481pub struct ListTriplesWith<'a, 'n, V: Vocabulary, G: LocalGenerator> {
482    vocabulary: &'n mut V,
483    generator: G,
484    rdf_direction: Option<RdfDirection>,
485    inner: ListTriples<'a, V::Iri, V::BlankId, V::Literal>,
486}
487
488impl<N: Vocabulary + VocabularyMut, G: LocalGenerator> Iterator for ListTriplesWith<'_, '_, N, G>
489where
490    N::Iri: Clone,
491    N::BlankId: Clone,
492    N::Literal: Clone,
493{
494    type Item = Triple<N::Iri, N::BlankId, N::Literal>;
495
496    fn next(&mut self) -> Option<Self::Item> {
497        self.inner.next(self.vocabulary, &mut self.generator, self.rdf_direction)
498    }
499}
500
501fn i18n(language: Option<&LangTagBuf>, direction: Direction) -> IriBuf {
502    let iri = match language {
503        Some(language) => format!("https://www.w3.org/ns/i18n#{language}_{direction}"),
504        None => format!("https://www.w3.org/ns/i18n#{direction}"),
505    };
506
507    // Built from constants and a parsed `LangTag` / `Direction`, so the
508    // bare-namespace fallback is unreachable.
509    IriBuf::new(iri).unwrap_or_else(|_| IriBuf::from(iri_rs::iri!("https://www.w3.org/ns/i18n#")))
510}
511
512/// RDF object value: either a node identifier or a literal handle.
513#[derive(Debug, Clone, PartialEq, Eq, Hash)]
514pub enum Value<T, B, L> {
515    /// A node identifier.
516    Id(ValidId<T, B>),
517    /// An RDF literal.
518    Literal(L),
519}
520
521impl<T, B, L> Value<T, B, L> {
522    /// Builds a value from a node identifier.
523    pub fn from_id(id: ValidId<T, B>) -> Self {
524        Self::Id(id)
525    }
526}