oxrdf/
literal.rs

1use crate::named_node::{NamedNode, NamedNodeRef};
2use crate::vocab::{rdf, xsd};
3use oxilangtag::{LanguageTag, LanguageTagParseError};
4#[cfg(feature = "oxsdatatypes")]
5use oxsdatatypes::*;
6use std::borrow::Cow;
7use std::fmt;
8use std::fmt::Write;
9
10/// An owned RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
11///
12/// The default string formatter is returning an N-Triples, Turtle, and SPARQL compatible representation:
13/// ```
14/// # use oxilangtag::LanguageTagParseError;
15/// use oxrdf::vocab::xsd;
16/// use oxrdf::Literal;
17///
18/// assert_eq!(
19///     "\"foo\\nbar\"",
20///     Literal::new_simple_literal("foo\nbar").to_string()
21/// );
22///
23/// assert_eq!(
24///     r#""1999-01-01"^^<http://www.w3.org/2001/XMLSchema#date>"#,
25///     Literal::new_typed_literal("1999-01-01", xsd::DATE).to_string()
26/// );
27///
28/// assert_eq!(
29///     r#""foo"@en"#,
30///     Literal::new_language_tagged_literal("foo", "en")?.to_string()
31/// );
32/// # Result::<_, LanguageTagParseError>::Ok(())
33/// ```
34#[derive(Eq, PartialEq, Debug, Clone, Hash)]
35pub struct Literal(LiteralContent);
36
37#[derive(PartialEq, Eq, Debug, Clone, Hash)]
38enum LiteralContent {
39    String(String),
40    LanguageTaggedString { value: String, language: String },
41    TypedLiteral { value: String, datatype: NamedNode },
42}
43
44impl Literal {
45    /// Builds an RDF [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal).
46    #[inline]
47    pub fn new_simple_literal(value: impl Into<String>) -> Self {
48        Self(LiteralContent::String(value.into()))
49    }
50
51    /// Builds an RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) with a [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
52    #[inline]
53    pub fn new_typed_literal(value: impl Into<String>, datatype: impl Into<NamedNode>) -> Self {
54        let value = value.into();
55        let datatype = datatype.into();
56        Self(if datatype == xsd::STRING {
57            LiteralContent::String(value)
58        } else {
59            LiteralContent::TypedLiteral { value, datatype }
60        })
61    }
62
63    /// Builds an RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
64    #[inline]
65    pub fn new_language_tagged_literal(
66        value: impl Into<String>,
67        language: impl Into<String>,
68    ) -> Result<Self, LanguageTagParseError> {
69        let mut language = language.into();
70        language.make_ascii_lowercase();
71        Ok(Self::new_language_tagged_literal_unchecked(
72            value,
73            LanguageTag::parse(language)?.into_inner(),
74        ))
75    }
76
77    /// Builds an RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
78    ///
79    /// It is the responsibility of the caller to check that `language`
80    /// is valid [BCP47](https://tools.ietf.org/html/bcp47) language tag,
81    /// and is lowercase.
82    ///
83    /// [`Literal::new_language_tagged_literal()`] is a safe version of this constructor and should be used for untrusted data.
84    #[inline]
85    pub fn new_language_tagged_literal_unchecked(
86        value: impl Into<String>,
87        language: impl Into<String>,
88    ) -> Self {
89        Self(LiteralContent::LanguageTaggedString {
90            value: value.into(),
91            language: language.into(),
92        })
93    }
94
95    /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form).
96    #[inline]
97    pub fn value(&self) -> &str {
98        self.as_ref().value()
99    }
100
101    /// The literal [language tag](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag) if it is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
102    ///
103    /// Language tags are defined by the [BCP47](https://tools.ietf.org/html/bcp47).
104    /// They are normalized to lowercase by this implementation.
105    #[inline]
106    pub fn language(&self) -> Option<&str> {
107        self.as_ref().language()
108    }
109
110    /// The literal [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
111    ///
112    /// The datatype of [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) is always [rdf:langString](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
113    /// The datatype of [simple literals](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) is [xsd:string](https://www.w3.org/TR/xmlschema11-2/#string).
114    #[inline]
115    pub fn datatype(&self) -> NamedNodeRef<'_> {
116        self.as_ref().datatype()
117    }
118
119    /// Checks if this literal could be seen as an RDF 1.0 [plain literal](https://www.w3.org/TR/2004/REC-rdf-concepts-20040210/#dfn-plain-literal).
120    ///
121    /// It returns true if the literal is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
122    /// or has the datatype [xsd:string](https://www.w3.org/TR/xmlschema11-2/#string).
123    #[inline]
124    pub fn is_plain(&self) -> bool {
125        self.as_ref().is_plain()
126    }
127
128    #[inline]
129    pub fn as_ref(&self) -> LiteralRef<'_> {
130        LiteralRef(match &self.0 {
131            LiteralContent::String(value) => LiteralRefContent::String(value),
132            LiteralContent::LanguageTaggedString { value, language } => {
133                LiteralRefContent::LanguageTaggedString { value, language }
134            }
135            LiteralContent::TypedLiteral { value, datatype } => LiteralRefContent::TypedLiteral {
136                value,
137                datatype: datatype.as_ref(),
138            },
139        })
140    }
141
142    /// Extract components from this literal (value, datatype and language tag).
143    #[inline]
144    pub fn destruct(self) -> (String, Option<NamedNode>, Option<String>) {
145        match self.0 {
146            LiteralContent::String(s) => (s, None, None),
147            LiteralContent::LanguageTaggedString { value, language } => {
148                (value, None, Some(language))
149            }
150            LiteralContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
151        }
152    }
153}
154
155impl fmt::Display for Literal {
156    #[inline]
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        self.as_ref().fmt(f)
159    }
160}
161
162impl<'a> From<&'a str> for Literal {
163    #[inline]
164    fn from(value: &'a str) -> Self {
165        Self(LiteralContent::String(value.into()))
166    }
167}
168
169impl From<String> for Literal {
170    #[inline]
171    fn from(value: String) -> Self {
172        Self(LiteralContent::String(value))
173    }
174}
175
176impl<'a> From<Cow<'a, str>> for Literal {
177    #[inline]
178    fn from(value: Cow<'a, str>) -> Self {
179        Self(LiteralContent::String(value.into()))
180    }
181}
182
183impl From<bool> for Literal {
184    #[inline]
185    fn from(value: bool) -> Self {
186        Self(LiteralContent::TypedLiteral {
187            value: value.to_string(),
188            datatype: xsd::BOOLEAN.into(),
189        })
190    }
191}
192
193impl From<i128> for Literal {
194    #[inline]
195    fn from(value: i128) -> Self {
196        Self(LiteralContent::TypedLiteral {
197            value: value.to_string(),
198            datatype: xsd::INTEGER.into(),
199        })
200    }
201}
202
203impl From<i64> for Literal {
204    #[inline]
205    fn from(value: i64) -> Self {
206        Self(LiteralContent::TypedLiteral {
207            value: value.to_string(),
208            datatype: xsd::INTEGER.into(),
209        })
210    }
211}
212
213impl From<i32> for Literal {
214    #[inline]
215    fn from(value: i32) -> Self {
216        Self(LiteralContent::TypedLiteral {
217            value: value.to_string(),
218            datatype: xsd::INTEGER.into(),
219        })
220    }
221}
222
223impl From<i16> for Literal {
224    #[inline]
225    fn from(value: i16) -> Self {
226        Self(LiteralContent::TypedLiteral {
227            value: value.to_string(),
228            datatype: xsd::INTEGER.into(),
229        })
230    }
231}
232
233impl From<u64> for Literal {
234    #[inline]
235    fn from(value: u64) -> Self {
236        Self(LiteralContent::TypedLiteral {
237            value: value.to_string(),
238            datatype: xsd::INTEGER.into(),
239        })
240    }
241}
242
243impl From<u32> for Literal {
244    #[inline]
245    fn from(value: u32) -> Self {
246        Self(LiteralContent::TypedLiteral {
247            value: value.to_string(),
248            datatype: xsd::INTEGER.into(),
249        })
250    }
251}
252
253impl From<u16> for Literal {
254    #[inline]
255    fn from(value: u16) -> Self {
256        Self(LiteralContent::TypedLiteral {
257            value: value.to_string(),
258            datatype: xsd::INTEGER.into(),
259        })
260    }
261}
262
263impl From<f32> for Literal {
264    #[inline]
265    fn from(value: f32) -> Self {
266        Self(LiteralContent::TypedLiteral {
267            value: if value == f32::INFINITY {
268                "INF".to_owned()
269            } else if value == f32::NEG_INFINITY {
270                "-INF".to_owned()
271            } else {
272                value.to_string()
273            },
274            datatype: xsd::FLOAT.into(),
275        })
276    }
277}
278
279impl From<f64> for Literal {
280    #[inline]
281    fn from(value: f64) -> Self {
282        Self(LiteralContent::TypedLiteral {
283            value: if value == f64::INFINITY {
284                "INF".to_owned()
285            } else if value == f64::NEG_INFINITY {
286                "-INF".to_owned()
287            } else {
288                value.to_string()
289            },
290            datatype: xsd::DOUBLE.into(),
291        })
292    }
293}
294
295#[cfg(feature = "oxsdatatypes")]
296impl From<Boolean> for Literal {
297    #[inline]
298    fn from(value: Boolean) -> Self {
299        Self::new_typed_literal(value.to_string(), xsd::BOOLEAN)
300    }
301}
302
303#[cfg(feature = "oxsdatatypes")]
304impl From<Float> for Literal {
305    #[inline]
306    fn from(value: Float) -> Self {
307        Self::new_typed_literal(value.to_string(), xsd::FLOAT)
308    }
309}
310
311#[cfg(feature = "oxsdatatypes")]
312impl From<Double> for Literal {
313    #[inline]
314    fn from(value: Double) -> Self {
315        Self::new_typed_literal(value.to_string(), xsd::DOUBLE)
316    }
317}
318
319#[cfg(feature = "oxsdatatypes")]
320impl From<Integer> for Literal {
321    #[inline]
322    fn from(value: Integer) -> Self {
323        Self::new_typed_literal(value.to_string(), xsd::INTEGER)
324    }
325}
326
327#[cfg(feature = "oxsdatatypes")]
328impl From<Decimal> for Literal {
329    #[inline]
330    fn from(value: Decimal) -> Self {
331        Self::new_typed_literal(value.to_string(), xsd::DECIMAL)
332    }
333}
334
335#[cfg(feature = "oxsdatatypes")]
336impl From<DateTime> for Literal {
337    #[inline]
338    fn from(value: DateTime) -> Self {
339        Self::new_typed_literal(value.to_string(), xsd::DATE_TIME)
340    }
341}
342
343#[cfg(feature = "oxsdatatypes")]
344impl From<Time> for Literal {
345    #[inline]
346    fn from(value: Time) -> Self {
347        Self::new_typed_literal(value.to_string(), xsd::TIME)
348    }
349}
350
351#[cfg(feature = "oxsdatatypes")]
352impl From<Date> for Literal {
353    #[inline]
354    fn from(value: Date) -> Self {
355        Self::new_typed_literal(value.to_string(), xsd::DATE)
356    }
357}
358
359#[cfg(feature = "oxsdatatypes")]
360impl From<GYearMonth> for Literal {
361    #[inline]
362    fn from(value: GYearMonth) -> Self {
363        Self::new_typed_literal(value.to_string(), xsd::G_YEAR_MONTH)
364    }
365}
366
367#[cfg(feature = "oxsdatatypes")]
368impl From<GYear> for Literal {
369    #[inline]
370    fn from(value: GYear) -> Self {
371        Self::new_typed_literal(value.to_string(), xsd::G_YEAR)
372    }
373}
374
375#[cfg(feature = "oxsdatatypes")]
376impl From<GMonthDay> for Literal {
377    #[inline]
378    fn from(value: GMonthDay) -> Self {
379        Self::new_typed_literal(value.to_string(), xsd::G_MONTH_DAY)
380    }
381}
382
383#[cfg(feature = "oxsdatatypes")]
384impl From<GMonth> for Literal {
385    #[inline]
386    fn from(value: GMonth) -> Self {
387        Self::new_typed_literal(value.to_string(), xsd::G_MONTH)
388    }
389}
390
391#[cfg(feature = "oxsdatatypes")]
392impl From<GDay> for Literal {
393    #[inline]
394    fn from(value: GDay) -> Self {
395        Self::new_typed_literal(value.to_string(), xsd::G_DAY)
396    }
397}
398
399#[cfg(feature = "oxsdatatypes")]
400impl From<Duration> for Literal {
401    #[inline]
402    fn from(value: Duration) -> Self {
403        Self::new_typed_literal(value.to_string(), xsd::DURATION)
404    }
405}
406
407#[cfg(feature = "oxsdatatypes")]
408impl From<YearMonthDuration> for Literal {
409    #[inline]
410    fn from(value: YearMonthDuration) -> Self {
411        Self::new_typed_literal(value.to_string(), xsd::YEAR_MONTH_DURATION)
412    }
413}
414
415#[cfg(feature = "oxsdatatypes")]
416impl From<DayTimeDuration> for Literal {
417    #[inline]
418    fn from(value: DayTimeDuration) -> Self {
419        Self::new_typed_literal(value.to_string(), xsd::DAY_TIME_DURATION)
420    }
421}
422
423/// A borrowed RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
424///
425/// The default string formatter is returning an N-Triples, Turtle, and SPARQL compatible representation:
426/// ```
427/// use oxrdf::vocab::xsd;
428/// use oxrdf::LiteralRef;
429///
430/// assert_eq!(
431///     "\"foo\\nbar\"",
432///     LiteralRef::new_simple_literal("foo\nbar").to_string()
433/// );
434///
435/// assert_eq!(
436///     r#""1999-01-01"^^<http://www.w3.org/2001/XMLSchema#date>"#,
437///     LiteralRef::new_typed_literal("1999-01-01", xsd::DATE).to_string()
438/// );
439/// ```
440#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
441pub struct LiteralRef<'a>(LiteralRefContent<'a>);
442
443#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
444enum LiteralRefContent<'a> {
445    String(&'a str),
446    LanguageTaggedString {
447        value: &'a str,
448        language: &'a str,
449    },
450    TypedLiteral {
451        value: &'a str,
452        datatype: NamedNodeRef<'a>,
453    },
454}
455
456impl<'a> LiteralRef<'a> {
457    /// Builds an RDF [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal).
458    #[inline]
459    pub const fn new_simple_literal(value: &'a str) -> Self {
460        LiteralRef(LiteralRefContent::String(value))
461    }
462
463    /// Builds an RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) with a [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
464    #[inline]
465    pub fn new_typed_literal(value: &'a str, datatype: impl Into<NamedNodeRef<'a>>) -> Self {
466        let datatype = datatype.into();
467        LiteralRef(if datatype == xsd::STRING {
468            LiteralRefContent::String(value)
469        } else {
470            LiteralRefContent::TypedLiteral { value, datatype }
471        })
472    }
473
474    /// Builds an RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
475    ///
476    /// It is the responsibility of the caller to check that `language`
477    /// is valid [BCP47](https://tools.ietf.org/html/bcp47) language tag,
478    /// and is lowercase.
479    ///
480    /// [`Literal::new_language_tagged_literal()`] is a safe version of this constructor and should be used for untrusted data.
481    #[inline]
482    pub const fn new_language_tagged_literal_unchecked(value: &'a str, language: &'a str) -> Self {
483        LiteralRef(LiteralRefContent::LanguageTaggedString { value, language })
484    }
485
486    /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form)
487    #[inline]
488    pub const fn value(self) -> &'a str {
489        match self.0 {
490            LiteralRefContent::String(value)
491            | LiteralRefContent::LanguageTaggedString { value, .. }
492            | LiteralRefContent::TypedLiteral { value, .. } => value,
493        }
494    }
495
496    /// The literal [language tag](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag) if it is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
497    ///
498    /// Language tags are defined by the [BCP47](https://tools.ietf.org/html/bcp47).
499    /// They are normalized to lowercase by this implementation.
500    #[inline]
501    pub const fn language(self) -> Option<&'a str> {
502        match self.0 {
503            LiteralRefContent::LanguageTaggedString { language, .. } => Some(language),
504            _ => None,
505        }
506    }
507
508    /// The literal [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
509    ///
510    /// The datatype of [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) is always [rdf:langString](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
511    /// The datatype of [simple literals](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) is [xsd:string](https://www.w3.org/TR/xmlschema11-2/#string).
512    #[inline]
513    pub const fn datatype(self) -> NamedNodeRef<'a> {
514        match self.0 {
515            LiteralRefContent::String(_) => xsd::STRING,
516            LiteralRefContent::LanguageTaggedString { .. } => rdf::LANG_STRING,
517            LiteralRefContent::TypedLiteral { datatype, .. } => datatype,
518        }
519    }
520
521    /// Checks if this literal could be seen as an RDF 1.0 [plain literal](https://www.w3.org/TR/2004/REC-rdf-concepts-20040210/#dfn-plain-literal).
522    ///
523    /// It returns true if the literal is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
524    /// or has the datatype [xsd:string](https://www.w3.org/TR/xmlschema11-2/#string).
525    #[inline]
526    pub const fn is_plain(self) -> bool {
527        matches!(
528            self.0,
529            LiteralRefContent::String(_) | LiteralRefContent::LanguageTaggedString { .. }
530        )
531    }
532
533    #[inline]
534    pub fn into_owned(self) -> Literal {
535        Literal(match self.0 {
536            LiteralRefContent::String(value) => LiteralContent::String(value.to_owned()),
537            LiteralRefContent::LanguageTaggedString { value, language } => {
538                LiteralContent::LanguageTaggedString {
539                    value: value.to_owned(),
540                    language: language.to_owned(),
541                }
542            }
543            LiteralRefContent::TypedLiteral { value, datatype } => LiteralContent::TypedLiteral {
544                value: value.to_owned(),
545                datatype: datatype.into_owned(),
546            },
547        })
548    }
549
550    /// Extract components from this literal
551    #[inline]
552    pub const fn destruct(self) -> (&'a str, Option<NamedNodeRef<'a>>, Option<&'a str>) {
553        match self.0 {
554            LiteralRefContent::String(s) => (s, None, None),
555            LiteralRefContent::LanguageTaggedString { value, language } => {
556                (value, None, Some(language))
557            }
558            LiteralRefContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
559        }
560    }
561}
562
563impl fmt::Display for LiteralRef<'_> {
564    #[inline]
565    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
566        match self.0 {
567            LiteralRefContent::String(value) => print_quoted_str(value, f),
568            LiteralRefContent::LanguageTaggedString { value, language } => {
569                print_quoted_str(value, f)?;
570                write!(f, "@{language}")
571            }
572            LiteralRefContent::TypedLiteral { value, datatype } => {
573                print_quoted_str(value, f)?;
574                write!(f, "^^{datatype}")
575            }
576        }
577    }
578}
579
580impl<'a> From<&'a Literal> for LiteralRef<'a> {
581    #[inline]
582    fn from(node: &'a Literal) -> Self {
583        node.as_ref()
584    }
585}
586
587impl<'a> From<LiteralRef<'a>> for Literal {
588    #[inline]
589    fn from(node: LiteralRef<'a>) -> Self {
590        node.into_owned()
591    }
592}
593
594impl<'a> From<&'a str> for LiteralRef<'a> {
595    #[inline]
596    fn from(value: &'a str) -> Self {
597        LiteralRef(LiteralRefContent::String(value))
598    }
599}
600
601impl PartialEq<Literal> for LiteralRef<'_> {
602    #[inline]
603    fn eq(&self, other: &Literal) -> bool {
604        *self == other.as_ref()
605    }
606}
607
608impl PartialEq<LiteralRef<'_>> for Literal {
609    #[inline]
610    fn eq(&self, other: &LiteralRef<'_>) -> bool {
611        self.as_ref() == *other
612    }
613}
614
615#[inline]
616pub fn print_quoted_str(string: &str, f: &mut impl Write) -> fmt::Result {
617    f.write_char('"')?;
618    for c in string.chars() {
619        match c {
620            '\u{08}' => f.write_str("\\b"),
621            '\t' => f.write_str("\\t"),
622            '\n' => f.write_str("\\n"),
623            '\u{0C}' => f.write_str("\\f"),
624            '\r' => f.write_str("\\r"),
625            '"' => f.write_str("\\\""),
626            '\\' => f.write_str("\\\\"),
627            '\0'..='\u{1F}' | '\u{7F}' => write!(f, "\\u{:04X}", u32::from(c)),
628            _ => f.write_char(c),
629        }?;
630    }
631    f.write_char('"')
632}
633
634#[cfg(test)]
635#[allow(clippy::panic_in_result_fn)]
636mod tests {
637    use super::*;
638
639    #[test]
640    fn test_simple_literal_equality() {
641        assert_eq!(
642            Literal::new_simple_literal("foo"),
643            Literal::new_typed_literal("foo", xsd::STRING)
644        );
645        assert_eq!(
646            Literal::new_simple_literal("foo"),
647            LiteralRef::new_typed_literal("foo", xsd::STRING)
648        );
649        assert_eq!(
650            LiteralRef::new_simple_literal("foo"),
651            Literal::new_typed_literal("foo", xsd::STRING)
652        );
653        assert_eq!(
654            LiteralRef::new_simple_literal("foo"),
655            LiteralRef::new_typed_literal("foo", xsd::STRING)
656        );
657    }
658
659    #[test]
660    fn test_float_format() {
661        assert_eq!("INF", Literal::from(f32::INFINITY).value());
662        assert_eq!("INF", Literal::from(f64::INFINITY).value());
663        assert_eq!("-INF", Literal::from(f32::NEG_INFINITY).value());
664        assert_eq!("-INF", Literal::from(f64::NEG_INFINITY).value());
665        assert_eq!("NaN", Literal::from(f32::NAN).value());
666        assert_eq!("NaN", Literal::from(f64::NAN).value());
667    }
668}