oxrdf/
literal.rs

1use crate::named_node::{NamedNode, NamedNodeRef};
2use crate::vocab::{rdf, xsd};
3use oxilangtag::{LanguageTag, LanguageTagParseError};
4#[cfg(feature = "oxsdatatypes")]
5use oxsdatatypes::*;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
8use std::borrow::Cow;
9use std::fmt;
10use std::fmt::Write;
11
12/// An owned RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
13///
14/// The default string formatter is returning an N-Triples, Turtle, and SPARQL compatible representation:
15/// ```
16/// # use oxilangtag::LanguageTagParseError;
17/// use oxrdf::Literal;
18/// use oxrdf::vocab::xsd;
19///
20/// assert_eq!(
21///     "\"foo\\nbar\"",
22///     Literal::new_simple_literal("foo\nbar").to_string()
23/// );
24///
25/// assert_eq!(
26///     r#""1999-01-01"^^<http://www.w3.org/2001/XMLSchema#date>"#,
27///     Literal::new_typed_literal("1999-01-01", xsd::DATE).to_string()
28/// );
29///
30/// assert_eq!(
31///     r#""foo"@en"#,
32///     Literal::new_language_tagged_literal("foo", "en")?.to_string()
33/// );
34/// # Result::<_, LanguageTagParseError>::Ok(())
35/// ```
36#[derive(Eq, PartialEq, Debug, Clone, Hash)]
37pub struct Literal(LiteralContent);
38
39#[derive(PartialEq, Eq, Debug, Clone, Hash)]
40enum LiteralContent {
41    String(String),
42    LanguageTaggedString {
43        value: String,
44        language: String,
45    },
46    #[cfg(feature = "rdf-12")]
47    DirectionalLanguageTaggedString {
48        value: String,
49        language: String,
50        direction: BaseDirection,
51    },
52    TypedLiteral {
53        value: String,
54        datatype: NamedNode,
55    },
56}
57
58impl Literal {
59    /// Builds an RDF [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal).
60    #[inline]
61    pub fn new_simple_literal(value: impl Into<String>) -> Self {
62        Self(LiteralContent::String(value.into()))
63    }
64
65    /// 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).
66    #[inline]
67    pub fn new_typed_literal(value: impl Into<String>, datatype: impl Into<NamedNode>) -> Self {
68        let value = value.into();
69        let datatype = datatype.into();
70        Self(if datatype == xsd::STRING {
71            LiteralContent::String(value)
72        } else {
73            LiteralContent::TypedLiteral { value, datatype }
74        })
75    }
76
77    /// Builds an RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
78    #[inline]
79    pub fn new_language_tagged_literal(
80        value: impl Into<String>,
81        language: impl Into<String>,
82    ) -> Result<Self, LanguageTagParseError> {
83        let mut language = language.into();
84        language.make_ascii_lowercase();
85        Ok(Self::new_language_tagged_literal_unchecked(
86            value,
87            LanguageTag::parse(language)?.into_inner(),
88        ))
89    }
90
91    /// Builds an RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
92    ///
93    /// It is the responsibility of the caller to check that `language`
94    /// is valid [BCP47](https://tools.ietf.org/html/bcp47) language tag,
95    /// and is lowercase.
96    ///
97    /// [`Literal::new_language_tagged_literal()`] is a safe version of this constructor and should be used for untrusted data.
98    #[inline]
99    pub fn new_language_tagged_literal_unchecked(
100        value: impl Into<String>,
101        language: impl Into<String>,
102    ) -> Self {
103        Self(LiteralContent::LanguageTaggedString {
104            value: value.into(),
105            language: language.into(),
106        })
107    }
108
109    /// Builds an RDF [directional language-tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-dir-lang-string).
110    #[cfg(feature = "rdf-12")]
111    #[inline]
112    pub fn new_directional_language_tagged_literal(
113        value: impl Into<String>,
114        language: impl Into<String>,
115        direction: impl Into<BaseDirection>,
116    ) -> Result<Self, LanguageTagParseError> {
117        let mut language = language.into();
118        language.make_ascii_lowercase();
119        Ok(Self::new_directional_language_tagged_literal_unchecked(
120            value,
121            LanguageTag::parse(language)?.into_inner(),
122            direction,
123        ))
124    }
125
126    /// Builds an RDF [directional language-tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-dir-lang-string).
127    ///
128    /// It is the responsibility of the caller to check that `language`
129    /// is valid [BCP47](https://tools.ietf.org/html/bcp47) language tag,
130    /// and is lowercase.
131    ///
132    /// [`Literal::new_language_tagged_literal()`] is a safe version of this constructor and should be used for untrusted data.
133    #[cfg(feature = "rdf-12")]
134    #[inline]
135    pub fn new_directional_language_tagged_literal_unchecked(
136        value: impl Into<String>,
137        language: impl Into<String>,
138        direction: impl Into<BaseDirection>,
139    ) -> Self {
140        Self(LiteralContent::DirectionalLanguageTaggedString {
141            value: value.into(),
142            language: language.into(),
143            direction: direction.into(),
144        })
145    }
146
147    /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form).
148    #[inline]
149    pub fn value(&self) -> &str {
150        self.as_ref().value()
151    }
152
153    /// 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).
154    ///
155    /// Language tags are defined by the [BCP47](https://tools.ietf.org/html/bcp47).
156    /// They are normalized to lowercase by this implementation.
157    #[inline]
158    pub fn language(&self) -> Option<&str> {
159        self.as_ref().language()
160    }
161
162    /// The literal [base direction](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction) if it is a [directional language-tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction).
163    ///
164    /// The two possible base directions are left-to-right (`ltr`) and right-to-left (`rtl`).
165    #[cfg(feature = "rdf-12")]
166    #[inline]
167    pub fn direction(&self) -> Option<BaseDirection> {
168        self.as_ref().direction()
169    }
170
171    /// The literal [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
172    ///
173    /// 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).
174    /// 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).
175    #[inline]
176    pub fn datatype(&self) -> NamedNodeRef<'_> {
177        self.as_ref().datatype()
178    }
179
180    /// 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).
181    ///
182    /// It returns true if the literal is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
183    /// or has the datatype [xsd:string](https://www.w3.org/TR/xmlschema11-2/#string).
184    #[inline]
185    #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
186    pub fn is_plain(&self) -> bool {
187        #[expect(deprecated)]
188        self.as_ref().is_plain()
189    }
190
191    #[inline]
192    pub fn as_ref(&self) -> LiteralRef<'_> {
193        LiteralRef(match &self.0 {
194            LiteralContent::String(value) => LiteralRefContent::String(value),
195            LiteralContent::LanguageTaggedString { value, language } => {
196                LiteralRefContent::LanguageTaggedString { value, language }
197            }
198            #[cfg(feature = "rdf-12")]
199            LiteralContent::DirectionalLanguageTaggedString {
200                value,
201                language,
202                direction,
203            } => LiteralRefContent::DirectionalLanguageTaggedString {
204                value,
205                language,
206                direction: *direction,
207            },
208            LiteralContent::TypedLiteral { value, datatype } => LiteralRefContent::TypedLiteral {
209                value,
210                datatype: datatype.as_ref(),
211            },
212        })
213    }
214
215    /// Extract components from this literal (value, datatype, language tag and base direction).
216    #[cfg(feature = "rdf-12")]
217    #[inline]
218    pub fn destruct(
219        self,
220    ) -> (
221        String,
222        Option<NamedNode>,
223        Option<String>,
224        Option<BaseDirection>,
225    ) {
226        match self.0 {
227            LiteralContent::String(s) => (s, None, None, None),
228            LiteralContent::LanguageTaggedString { value, language } => {
229                (value, None, Some(language), None)
230            }
231            LiteralContent::DirectionalLanguageTaggedString {
232                value,
233                language,
234                direction,
235            } => (value, None, Some(language), Some(direction)),
236            LiteralContent::TypedLiteral { value, datatype } => (value, Some(datatype), None, None),
237        }
238    }
239
240    /// Extract components from this literal (value, datatype and language tag).
241    #[cfg(not(feature = "rdf-12"))]
242    #[inline]
243    pub fn destruct(self) -> (String, Option<NamedNode>, Option<String>) {
244        match self.0 {
245            LiteralContent::String(s) => (s, None, None),
246            LiteralContent::LanguageTaggedString { value, language } => {
247                (value, None, Some(language))
248            }
249            LiteralContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
250        }
251    }
252}
253
254impl fmt::Display for Literal {
255    #[inline]
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        self.as_ref().fmt(f)
258    }
259}
260
261impl<'a> From<&'a str> for Literal {
262    #[inline]
263    fn from(value: &'a str) -> Self {
264        Self(LiteralContent::String(value.into()))
265    }
266}
267
268impl From<String> for Literal {
269    #[inline]
270    fn from(value: String) -> Self {
271        Self(LiteralContent::String(value))
272    }
273}
274
275impl<'a> From<Cow<'a, str>> for Literal {
276    #[inline]
277    fn from(value: Cow<'a, str>) -> Self {
278        Self(LiteralContent::String(value.into()))
279    }
280}
281
282impl From<bool> for Literal {
283    #[inline]
284    fn from(value: bool) -> Self {
285        Self(LiteralContent::TypedLiteral {
286            value: value.to_string(),
287            datatype: xsd::BOOLEAN.into(),
288        })
289    }
290}
291
292impl From<i128> for Literal {
293    #[inline]
294    fn from(value: i128) -> Self {
295        Self(LiteralContent::TypedLiteral {
296            value: value.to_string(),
297            datatype: xsd::INTEGER.into(),
298        })
299    }
300}
301
302impl From<i64> for Literal {
303    #[inline]
304    fn from(value: i64) -> Self {
305        Self(LiteralContent::TypedLiteral {
306            value: value.to_string(),
307            datatype: xsd::INTEGER.into(),
308        })
309    }
310}
311
312impl From<i32> for Literal {
313    #[inline]
314    fn from(value: i32) -> Self {
315        Self(LiteralContent::TypedLiteral {
316            value: value.to_string(),
317            datatype: xsd::INTEGER.into(),
318        })
319    }
320}
321
322impl From<i16> for Literal {
323    #[inline]
324    fn from(value: i16) -> Self {
325        Self(LiteralContent::TypedLiteral {
326            value: value.to_string(),
327            datatype: xsd::INTEGER.into(),
328        })
329    }
330}
331
332impl From<u64> for Literal {
333    #[inline]
334    fn from(value: u64) -> Self {
335        Self(LiteralContent::TypedLiteral {
336            value: value.to_string(),
337            datatype: xsd::INTEGER.into(),
338        })
339    }
340}
341
342impl From<u32> for Literal {
343    #[inline]
344    fn from(value: u32) -> Self {
345        Self(LiteralContent::TypedLiteral {
346            value: value.to_string(),
347            datatype: xsd::INTEGER.into(),
348        })
349    }
350}
351
352impl From<u16> for Literal {
353    #[inline]
354    fn from(value: u16) -> Self {
355        Self(LiteralContent::TypedLiteral {
356            value: value.to_string(),
357            datatype: xsd::INTEGER.into(),
358        })
359    }
360}
361
362impl From<f32> for Literal {
363    #[inline]
364    fn from(value: f32) -> Self {
365        Self(LiteralContent::TypedLiteral {
366            value: if value == f32::INFINITY {
367                "INF".to_owned()
368            } else if value == f32::NEG_INFINITY {
369                "-INF".to_owned()
370            } else {
371                value.to_string()
372            },
373            datatype: xsd::FLOAT.into(),
374        })
375    }
376}
377
378impl From<f64> for Literal {
379    #[inline]
380    fn from(value: f64) -> Self {
381        Self(LiteralContent::TypedLiteral {
382            value: if value == f64::INFINITY {
383                "INF".to_owned()
384            } else if value == f64::NEG_INFINITY {
385                "-INF".to_owned()
386            } else {
387                value.to_string()
388            },
389            datatype: xsd::DOUBLE.into(),
390        })
391    }
392}
393
394#[cfg(feature = "oxsdatatypes")]
395impl From<Boolean> for Literal {
396    #[inline]
397    fn from(value: Boolean) -> Self {
398        Self::new_typed_literal(value.to_string(), xsd::BOOLEAN)
399    }
400}
401
402#[cfg(feature = "oxsdatatypes")]
403impl From<Float> for Literal {
404    #[inline]
405    fn from(value: Float) -> Self {
406        Self::new_typed_literal(value.to_string(), xsd::FLOAT)
407    }
408}
409
410#[cfg(feature = "oxsdatatypes")]
411impl From<Double> for Literal {
412    #[inline]
413    fn from(value: Double) -> Self {
414        Self::new_typed_literal(value.to_string(), xsd::DOUBLE)
415    }
416}
417
418#[cfg(feature = "oxsdatatypes")]
419impl From<Integer> for Literal {
420    #[inline]
421    fn from(value: Integer) -> Self {
422        Self::new_typed_literal(value.to_string(), xsd::INTEGER)
423    }
424}
425
426#[cfg(feature = "oxsdatatypes")]
427impl From<Decimal> for Literal {
428    #[inline]
429    fn from(value: Decimal) -> Self {
430        Self::new_typed_literal(value.to_string(), xsd::DECIMAL)
431    }
432}
433
434#[cfg(feature = "oxsdatatypes")]
435impl From<DateTime> for Literal {
436    #[inline]
437    fn from(value: DateTime) -> Self {
438        Self::new_typed_literal(value.to_string(), xsd::DATE_TIME)
439    }
440}
441
442#[cfg(feature = "oxsdatatypes")]
443impl From<Time> for Literal {
444    #[inline]
445    fn from(value: Time) -> Self {
446        Self::new_typed_literal(value.to_string(), xsd::TIME)
447    }
448}
449
450#[cfg(feature = "oxsdatatypes")]
451impl From<Date> for Literal {
452    #[inline]
453    fn from(value: Date) -> Self {
454        Self::new_typed_literal(value.to_string(), xsd::DATE)
455    }
456}
457
458#[cfg(feature = "oxsdatatypes")]
459impl From<GYearMonth> for Literal {
460    #[inline]
461    fn from(value: GYearMonth) -> Self {
462        Self::new_typed_literal(value.to_string(), xsd::G_YEAR_MONTH)
463    }
464}
465
466#[cfg(feature = "oxsdatatypes")]
467impl From<GYear> for Literal {
468    #[inline]
469    fn from(value: GYear) -> Self {
470        Self::new_typed_literal(value.to_string(), xsd::G_YEAR)
471    }
472}
473
474#[cfg(feature = "oxsdatatypes")]
475impl From<GMonthDay> for Literal {
476    #[inline]
477    fn from(value: GMonthDay) -> Self {
478        Self::new_typed_literal(value.to_string(), xsd::G_MONTH_DAY)
479    }
480}
481
482#[cfg(feature = "oxsdatatypes")]
483impl From<GMonth> for Literal {
484    #[inline]
485    fn from(value: GMonth) -> Self {
486        Self::new_typed_literal(value.to_string(), xsd::G_MONTH)
487    }
488}
489
490#[cfg(feature = "oxsdatatypes")]
491impl From<GDay> for Literal {
492    #[inline]
493    fn from(value: GDay) -> Self {
494        Self::new_typed_literal(value.to_string(), xsd::G_DAY)
495    }
496}
497
498#[cfg(feature = "oxsdatatypes")]
499impl From<Duration> for Literal {
500    #[inline]
501    fn from(value: Duration) -> Self {
502        Self::new_typed_literal(value.to_string(), xsd::DURATION)
503    }
504}
505
506#[cfg(feature = "oxsdatatypes")]
507impl From<YearMonthDuration> for Literal {
508    #[inline]
509    fn from(value: YearMonthDuration) -> Self {
510        Self::new_typed_literal(value.to_string(), xsd::YEAR_MONTH_DURATION)
511    }
512}
513
514#[cfg(feature = "oxsdatatypes")]
515impl From<DayTimeDuration> for Literal {
516    #[inline]
517    fn from(value: DayTimeDuration) -> Self {
518        Self::new_typed_literal(value.to_string(), xsd::DAY_TIME_DURATION)
519    }
520}
521
522/// A borrowed RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
523///
524/// The default string formatter is returning an N-Triples, Turtle, and SPARQL compatible representation:
525/// ```
526/// use oxrdf::LiteralRef;
527/// use oxrdf::vocab::xsd;
528///
529/// assert_eq!(
530///     "\"foo\\nbar\"",
531///     LiteralRef::new_simple_literal("foo\nbar").to_string()
532/// );
533///
534/// assert_eq!(
535///     r#""1999-01-01"^^<http://www.w3.org/2001/XMLSchema#date>"#,
536///     LiteralRef::new_typed_literal("1999-01-01", xsd::DATE).to_string()
537/// );
538/// ```
539#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
540pub struct LiteralRef<'a>(LiteralRefContent<'a>);
541
542#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
543enum LiteralRefContent<'a> {
544    String(&'a str),
545    LanguageTaggedString {
546        value: &'a str,
547        language: &'a str,
548    },
549    #[cfg(feature = "rdf-12")]
550    DirectionalLanguageTaggedString {
551        value: &'a str,
552        language: &'a str,
553        direction: BaseDirection,
554    },
555    TypedLiteral {
556        value: &'a str,
557        datatype: NamedNodeRef<'a>,
558    },
559}
560
561impl<'a> LiteralRef<'a> {
562    /// Builds an RDF [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal).
563    #[inline]
564    pub const fn new_simple_literal(value: &'a str) -> Self {
565        LiteralRef(LiteralRefContent::String(value))
566    }
567
568    /// 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).
569    #[inline]
570    pub fn new_typed_literal(value: &'a str, datatype: impl Into<NamedNodeRef<'a>>) -> Self {
571        let datatype = datatype.into();
572        LiteralRef(if datatype == xsd::STRING {
573            LiteralRefContent::String(value)
574        } else {
575            LiteralRefContent::TypedLiteral { value, datatype }
576        })
577    }
578
579    /// Builds an RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
580    ///
581    /// It is the responsibility of the caller to check that `language`
582    /// is valid [BCP47](https://tools.ietf.org/html/bcp47) language tag,
583    /// and is lowercase.
584    ///
585    /// [`Literal::new_language_tagged_literal()`] is a safe version of this constructor and should be used for untrusted data.
586    #[inline]
587    pub const fn new_language_tagged_literal_unchecked(value: &'a str, language: &'a str) -> Self {
588        LiteralRef(LiteralRefContent::LanguageTaggedString { value, language })
589    }
590
591    /// Builds an RDF [directional language-tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-dir-lang-string).
592    ///
593    /// It is the responsibility of the caller to check that `language`
594    /// is valid [BCP47](https://tools.ietf.org/html/bcp47) language tag,
595    /// and is lowercase.
596    ///
597    /// [`Literal::new_directional_language_tagged_literal()`] is a safe version of this constructor and should be used for untrusted data.
598    #[cfg(feature = "rdf-12")]
599    #[inline]
600    pub const fn new_directional_language_tagged_literal_unchecked(
601        value: &'a str,
602        language: &'a str,
603        direction: BaseDirection,
604    ) -> Self {
605        LiteralRef(LiteralRefContent::DirectionalLanguageTaggedString {
606            value,
607            language,
608            direction,
609        })
610    }
611
612    /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form)
613    #[inline]
614    pub const fn value(self) -> &'a str {
615        match self.0 {
616            LiteralRefContent::String(value)
617            | LiteralRefContent::LanguageTaggedString { value, .. }
618            | LiteralRefContent::TypedLiteral { value, .. } => value,
619            #[cfg(feature = "rdf-12")]
620            LiteralRefContent::DirectionalLanguageTaggedString { value, .. } => value,
621        }
622    }
623
624    /// 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).
625    ///
626    /// Language tags are defined by the [BCP47](https://tools.ietf.org/html/bcp47).
627    /// They are normalized to lowercase by this implementation.
628    #[inline]
629    pub const fn language(self) -> Option<&'a str> {
630        match self.0 {
631            LiteralRefContent::LanguageTaggedString { language, .. } => Some(language),
632            #[cfg(feature = "rdf-12")]
633            LiteralRefContent::DirectionalLanguageTaggedString { language, .. } => Some(language),
634            _ => None,
635        }
636    }
637
638    /// The literal [base direction](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction) if it is a [directional language-tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction).
639    ///
640    /// The two possible base directions are left-to-right (`ltr`) and right-to-left (`rtl`).
641    #[cfg(feature = "rdf-12")]
642    #[inline]
643    pub const fn direction(self) -> Option<BaseDirection> {
644        match self.0 {
645            LiteralRefContent::DirectionalLanguageTaggedString { direction, .. } => Some(direction),
646            _ => None,
647        }
648    }
649
650    /// The literal [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
651    ///
652    /// 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).
653    /// 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).
654    #[inline]
655    pub const fn datatype(self) -> NamedNodeRef<'a> {
656        match self.0 {
657            LiteralRefContent::String(_) => xsd::STRING,
658            LiteralRefContent::LanguageTaggedString { .. } => rdf::LANG_STRING,
659            #[cfg(feature = "rdf-12")]
660            LiteralRefContent::DirectionalLanguageTaggedString { .. } => rdf::DIR_LANG_STRING,
661            LiteralRefContent::TypedLiteral { datatype, .. } => datatype,
662        }
663    }
664
665    /// 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).
666    ///
667    /// It returns true if the literal is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
668    /// or has the datatype [xsd:string](https://www.w3.org/TR/xmlschema11-2/#string).
669    #[inline]
670    #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
671    pub const fn is_plain(self) -> bool {
672        matches!(
673            self.0,
674            LiteralRefContent::String(_) | LiteralRefContent::LanguageTaggedString { .. }
675        )
676    }
677
678    #[inline]
679    pub fn into_owned(self) -> Literal {
680        Literal(match self.0 {
681            LiteralRefContent::String(value) => LiteralContent::String(value.to_owned()),
682            LiteralRefContent::LanguageTaggedString { value, language } => {
683                LiteralContent::LanguageTaggedString {
684                    value: value.to_owned(),
685                    language: language.to_owned(),
686                }
687            }
688            #[cfg(feature = "rdf-12")]
689            LiteralRefContent::DirectionalLanguageTaggedString {
690                value,
691                language,
692                direction,
693            } => LiteralContent::DirectionalLanguageTaggedString {
694                value: value.to_owned(),
695                language: language.to_owned(),
696                direction,
697            },
698            LiteralRefContent::TypedLiteral { value, datatype } => LiteralContent::TypedLiteral {
699                value: value.to_owned(),
700                datatype: datatype.into_owned(),
701            },
702        })
703    }
704
705    /// Extract components from this literal
706    #[cfg(not(feature = "rdf-12"))]
707    #[inline]
708    #[deprecated(
709        note = "Use directly .value(), .datatype() and .language()",
710        since = "0.3.0"
711    )]
712    pub const fn destruct(self) -> (&'a str, Option<NamedNodeRef<'a>>, Option<&'a str>) {
713        match self.0 {
714            LiteralRefContent::String(s) => (s, None, None),
715            LiteralRefContent::LanguageTaggedString { value, language } => {
716                (value, None, Some(language))
717            }
718            LiteralRefContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
719        }
720    }
721}
722
723impl fmt::Display for LiteralRef<'_> {
724    #[inline]
725    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726        match self.0 {
727            LiteralRefContent::String(value) => print_quoted_str(value, f),
728            LiteralRefContent::LanguageTaggedString { value, language } => {
729                print_quoted_str(value, f)?;
730                write!(f, "@{language}")
731            }
732            #[cfg(feature = "rdf-12")]
733            LiteralRefContent::DirectionalLanguageTaggedString {
734                value,
735                language,
736                direction,
737            } => {
738                print_quoted_str(value, f)?;
739                write!(f, "@{language}--{direction}")
740            }
741            LiteralRefContent::TypedLiteral { value, datatype } => {
742                print_quoted_str(value, f)?;
743                write!(f, "^^{datatype}")
744            }
745        }
746    }
747}
748
749impl<'a> From<&'a Literal> for LiteralRef<'a> {
750    #[inline]
751    fn from(node: &'a Literal) -> Self {
752        node.as_ref()
753    }
754}
755
756impl<'a> From<LiteralRef<'a>> for Literal {
757    #[inline]
758    fn from(node: LiteralRef<'a>) -> Self {
759        node.into_owned()
760    }
761}
762
763impl<'a> From<&'a str> for LiteralRef<'a> {
764    #[inline]
765    fn from(value: &'a str) -> Self {
766        LiteralRef(LiteralRefContent::String(value))
767    }
768}
769
770impl PartialEq<Literal> for LiteralRef<'_> {
771    #[inline]
772    fn eq(&self, other: &Literal) -> bool {
773        *self == other.as_ref()
774    }
775}
776
777impl PartialEq<LiteralRef<'_>> for Literal {
778    #[inline]
779    fn eq(&self, other: &LiteralRef<'_>) -> bool {
780        self.as_ref() == *other
781    }
782}
783
784#[inline]
785pub fn print_quoted_str(string: &str, f: &mut impl Write) -> fmt::Result {
786    f.write_char('"')?;
787    for c in string.chars() {
788        match c {
789            '\u{8}' => f.write_str("\\b"),
790            '\t' => f.write_str("\\t"),
791            '\n' => f.write_str("\\n"),
792            '\u{C}' => f.write_str("\\f"),
793            '\r' => f.write_str("\\r"),
794            '"' => f.write_str("\\\""),
795            '\\' => f.write_str("\\\\"),
796            '\0'..='\u{1F}' | '\u{7F}' | '\u{FFFE}' | '\u{FFFF}' => {
797                write!(f, "\\u{:04X}", u32::from(c))
798            }
799            _ => f.write_char(c),
800        }?;
801    }
802    f.write_char('"')
803}
804
805/// A [directional language-tagged string](https://www.w3.org/TR/rdf12-concepts/#dfn-dir-lang-string) [base-direction](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction)
806#[cfg(feature = "rdf-12")]
807#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
808#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
809pub enum BaseDirection {
810    /// the initial text direction is set to left-to-right
811    #[cfg_attr(feature = "serde", serde(rename = "ltr"))]
812    Ltr,
813    /// the initial text direction is set to right-to-left
814    #[cfg_attr(feature = "serde", serde(rename = "rtl"))]
815    Rtl,
816}
817
818#[cfg(feature = "rdf-12")]
819impl fmt::Display for BaseDirection {
820    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
821        f.write_str(match self {
822            Self::Ltr => "ltr",
823            Self::Rtl => "rtl",
824        })
825    }
826}
827
828#[cfg(feature = "serde")]
829impl Serialize for Literal {
830    #[inline]
831    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
832        self.as_ref().serialize(serializer)
833    }
834}
835
836#[cfg(feature = "serde")]
837impl Serialize for LiteralRef<'_> {
838    #[inline]
839    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
840        #[expect(clippy::struct_field_names)]
841        #[derive(Serialize)]
842        #[serde(rename = "Literal")]
843        struct Value<'a> {
844            value: &'a str,
845            #[serde(skip_serializing_if = "Option::is_none")]
846            language: Option<&'a str>,
847            #[cfg(feature = "rdf-12")]
848            #[serde(skip_serializing_if = "Option::is_none")]
849            direction: Option<BaseDirection>,
850            #[serde(skip_serializing_if = "Option::is_none")]
851            datatype: Option<&'a str>,
852        }
853        match self.0 {
854            LiteralRefContent::String(value) => Value {
855                value,
856                language: None,
857                #[cfg(feature = "rdf-12")]
858                direction: None,
859                datatype: None,
860            },
861            LiteralRefContent::LanguageTaggedString { value, language } => Value {
862                value,
863                language: Some(language),
864                #[cfg(feature = "rdf-12")]
865                direction: None,
866                datatype: None,
867            },
868            #[cfg(feature = "rdf-12")]
869            LiteralRefContent::DirectionalLanguageTaggedString {
870                value,
871                language,
872                direction,
873            } => Value {
874                value,
875                language: Some(language),
876                direction: Some(direction),
877                datatype: None,
878            },
879            LiteralRefContent::TypedLiteral { value, datatype } => Value {
880                value,
881                language: None,
882                #[cfg(feature = "rdf-12")]
883                direction: None,
884                datatype: Some(datatype.as_str()),
885            },
886        }
887        .serialize(serializer)
888    }
889}
890
891#[cfg(feature = "serde")]
892impl<'de> Deserialize<'de> for Literal {
893    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
894    where
895        D: Deserializer<'de>,
896    {
897        #[expect(clippy::struct_field_names)]
898        #[derive(Deserialize)]
899        #[serde(rename = "Literal")]
900        struct Value {
901            value: String,
902            language: Option<String>,
903            #[cfg(feature = "rdf-12")]
904            direction: Option<BaseDirection>,
905            datatype: Option<String>,
906        }
907        let Value {
908            value,
909            language,
910            #[cfg(feature = "rdf-12")]
911            direction,
912            datatype,
913        } = Value::deserialize(deserializer)?;
914        if let Some(language) = language {
915            #[cfg(feature = "rdf-12")]
916            if let Some(direction) = direction {
917                return Literal::new_directional_language_tagged_literal(
918                    value, language, direction,
919                )
920                .map_err(de::Error::custom);
921            }
922            Literal::new_language_tagged_literal(value, language).map_err(de::Error::custom)
923        } else if let Some(datatype) = datatype {
924            Ok(Literal::new_typed_literal(
925                value,
926                NamedNode::new(datatype).map_err(de::Error::custom)?,
927            ))
928        } else {
929            Ok(Literal::new_simple_literal(value))
930        }
931    }
932}
933
934#[cfg(test)]
935mod tests {
936    use super::*;
937
938    #[test]
939    fn test_simple_literal_equality() {
940        assert_eq!(
941            Literal::new_simple_literal("foo"),
942            Literal::new_typed_literal("foo", xsd::STRING)
943        );
944        assert_eq!(
945            Literal::new_simple_literal("foo"),
946            LiteralRef::new_typed_literal("foo", xsd::STRING)
947        );
948        assert_eq!(
949            LiteralRef::new_simple_literal("foo"),
950            Literal::new_typed_literal("foo", xsd::STRING)
951        );
952        assert_eq!(
953            LiteralRef::new_simple_literal("foo"),
954            LiteralRef::new_typed_literal("foo", xsd::STRING)
955        );
956    }
957
958    #[test]
959    fn test_float_format() {
960        assert_eq!("INF", Literal::from(f32::INFINITY).value());
961        assert_eq!("INF", Literal::from(f64::INFINITY).value());
962        assert_eq!("-INF", Literal::from(f32::NEG_INFINITY).value());
963        assert_eq!("-INF", Literal::from(f64::NEG_INFINITY).value());
964        assert_eq!("NaN", Literal::from(f32::NAN).value());
965        assert_eq!("NaN", Literal::from(f64::NAN).value());
966    }
967
968    #[test]
969    #[cfg(feature = "serde")]
970    fn test_serde() {
971        // Simple literal
972        let simple = Literal::new_simple_literal("foo");
973        let j = serde_json::to_string(&simple).unwrap();
974        assert_eq!("{\"value\":\"foo\"}", j);
975        let simple2: Literal = serde_json::from_str(&j).unwrap();
976        assert_eq!(simple, simple2);
977
978        // Typed literal
979        let typed = Literal::new_typed_literal("foo", xsd::BOOLEAN);
980        let j = serde_json::to_string(&typed).unwrap();
981        assert_eq!(
982            "{\"value\":\"foo\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#boolean\"}",
983            j
984        );
985        let typed2: Literal = serde_json::from_str(&j).unwrap();
986        assert_eq!(typed, typed2);
987
988        // Language-tagged string
989        let lt = Literal::new_language_tagged_literal("foo", "en").unwrap();
990        let j = serde_json::to_string(&lt).unwrap();
991        assert_eq!("{\"value\":\"foo\",\"language\":\"en\"}", j);
992        let lt2: Literal = serde_json::from_str(&j).unwrap();
993        assert_eq!(lt, lt2);
994    }
995}