sophia_rio/
serializer.rs

1//! Common implementations for adapting
2//! [RIO](https://docs.rs/rio_api/) serializers.
3
4use rio_api::formatter::{QuadsFormatter, TriplesFormatter};
5use rio_api::model::{
6    BlankNode, GraphName as RioGraphName, Literal, NamedNode, Quad as RioQuad, Subject,
7    Term as RioTerm, Triple as RioTriple,
8};
9use sophia_api::ns::xsd;
10use sophia_api::quad::Quad;
11use sophia_api::source::{QuadSource, StreamResult, TripleSource};
12use sophia_api::term::{FromTerm, SimpleTerm};
13use sophia_api::triple::Triple;
14
15/// Format all standard RDF triples of `triples` using `tf`.
16///
17/// NB: non-standard (generalized) RDF triples will be silently ignored.
18pub fn rio_format_triples<TF, TS>(
19    tf: &mut TF,
20    mut triples: TS,
21) -> StreamResult<(), TS::Error, TF::Error>
22where
23    TF: TriplesFormatter,
24    TS: TripleSource,
25{
26    triples.try_for_each_triple(|t| {
27        let t = t.to_spo().map(SimpleTerm::from_term);
28        match convert_triple(&t, Empty).head() {
29            None => Ok(()),
30            Some(head) => tf.format(head),
31        }
32    })
33}
34
35/// Format all standard RDF quads of `quads` using `qf`.
36///
37/// NB: non-standard (generalized) RDF quads will be silently ignored.
38pub fn rio_format_quads<QF, QS>(
39    qf: &mut QF,
40    mut quads: QS,
41) -> StreamResult<(), QS::Error, QF::Error>
42where
43    QF: QuadsFormatter,
44    QS: QuadSource,
45{
46    quads.try_for_each_quad(|q| {
47        let (t, g) = q.to_spog();
48        let t = t.map(SimpleTerm::from_term);
49        let g = g.map(SimpleTerm::from_term);
50        let graph_name: Option<RioGraphName> = match &g {
51            None => None,
52            Some(SimpleTerm::Iri(iri)) => Some(NamedNode { iri }.into()),
53            Some(SimpleTerm::BlankNode(id)) => Some(BlankNode { id }.into()),
54            _ => {
55                return Ok(());
56            }
57        };
58        match convert_triple(&t, Empty).head() {
59            None => Ok(()),
60            Some(RioTriple {
61                subject,
62                predicate,
63                object,
64            }) => {
65                let q = RioQuad {
66                    subject: *subject,
67                    predicate: *predicate,
68                    object: *object,
69                    graph_name,
70                };
71                qf.format(&q)
72            }
73        }
74    })
75}
76
77/// Convert this triple of SimpleTerms to a RioTriple if possible
78/// (i.e. if it is a strict RDF triple)
79fn convert_triple<'a>(
80    t: &'a [SimpleTerm<'a>; 3],
81    mut stack: Stack<RioTriple<'a>>,
82) -> Stack<RioTriple<'a>> {
83    let subject = match t.s() {
84        SimpleTerm::Iri(iri) => NamedNode { iri }.into(),
85        SimpleTerm::BlankNode(id) => BlankNode { id }.into(),
86        SimpleTerm::Triple(triple) => {
87            stack = convert_triple(triple, stack);
88            // Safety: the line below is safe because the triple will then be inserted in the same stack
89            match unsafe { stack.head2() } {
90                Some(t) => Subject::Triple(t),
91                None => {
92                    return Empty;
93                }
94            }
95        }
96        _ => {
97            return Empty;
98        }
99    };
100    let predicate = match t.p() {
101        SimpleTerm::Iri(iri) => NamedNode { iri },
102        _ => {
103            return Empty;
104        }
105    };
106    let object = match t.o() {
107        SimpleTerm::Iri(iri) => NamedNode { iri }.into(),
108        SimpleTerm::BlankNode(id) => BlankNode { id }.into(),
109        SimpleTerm::LiteralDatatype(value, iri) if xsd::string == iri => {
110            Literal::Simple { value }.into()
111        }
112        SimpleTerm::LiteralDatatype(value, iri) => Literal::Typed {
113            value,
114            datatype: NamedNode { iri },
115        }
116        .into(),
117        SimpleTerm::LiteralLanguage(value, language) => {
118            Literal::LanguageTaggedString { value, language }.into()
119        }
120        SimpleTerm::Triple(triple) => {
121            stack = convert_triple(triple, stack);
122            // Safety: the line below is safe because the triple will then be inserted in the same stack
123            match unsafe { stack.head2() } {
124                Some(t) => RioTerm::Triple(t),
125                None => {
126                    return Empty;
127                }
128            }
129        }
130        _ => {
131            return Empty;
132        }
133    };
134    Stack::Node(Box::new((
135        RioTriple {
136            subject,
137            predicate,
138            object,
139        },
140        stack,
141    )))
142}
143
144enum Stack<T> {
145    Empty,
146    Node(Box<(T, Stack<T>)>),
147}
148use Stack::*;
149impl<T> Stack<T> {
150    /// Get the triple at the head of the stack.
151    fn head(&self) -> Option<&T> {
152        match self {
153            Empty => None,
154            Node(b) => Some(&b.0),
155        }
156    }
157    /// Get the triple at the head of the stack,
158    /// *for an arbitrary lifetime*.
159    /// The last part obviously makes it unsafe.
160    /// Therefore, this method must only be called when the output lifetime 'a
161    /// is known to be safe.
162    ///
163    /// Good examples are:
164    /// * when the returned triple will be used as a constituent of another triple in the same stack
165    ///   (the constituent will stay in the stack longer than the nested triple)
166    /// * when the returned triple will not live as long as the stack itself
167    unsafe fn head2<'a>(&self) -> Option<&'a T>
168    where
169        Self: 'a,
170        T: 'a,
171    {
172        self.head().map(|h| std::mem::transmute(h))
173    }
174}