1use crate::model::{NamedNode, NamedNodeRef, ObjectTerm, RdfTerm};
7use crate::vocab::{rdf, xsd};
8use crate::OxirsError;
9use oxilangtag::LanguageTag as OxiLanguageTag;
10use oxsdatatypes::{Boolean, Date, DateTime, Decimal, Double, Float, Integer, Time};
11use std::borrow::Cow;
12use std::fmt::{self, Write};
13use std::hash::Hash;
14use std::str::FromStr;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct LanguageTagParseError {
19 message: String,
20}
21
22impl fmt::Display for LanguageTagParseError {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "Language tag parse error: {}", self.message)
25 }
26}
27
28impl std::error::Error for LanguageTagParseError {}
29
30impl From<LanguageTagParseError> for OxirsError {
31 fn from(err: LanguageTagParseError) -> Self {
32 OxirsError::Parse(err.message)
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub struct LanguageTag {
39 tag: String,
40}
41
42impl LanguageTag {
43 pub fn parse(tag: impl Into<String>) -> Result<Self, LanguageTagParseError> {
45 let tag = tag.into();
46 validate_language_tag(&tag)?;
47 Ok(LanguageTag { tag })
48 }
49
50 pub fn as_str(&self) -> &str {
52 &self.tag
53 }
54
55 pub fn into_inner(self) -> String {
57 self.tag
58 }
59}
60
61impl fmt::Display for LanguageTag {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.write_str(&self.tag)
64 }
65}
66
67fn validate_language_tag(tag: &str) -> Result<(), LanguageTagParseError> {
69 OxiLanguageTag::parse(tag)
70 .map(|_| ())
71 .map_err(|e| LanguageTagParseError {
72 message: format!("Invalid language tag '{tag}': {e}"),
73 })
74}
75
76pub fn validate_xsd_value(value: &str, datatype_iri: &str) -> Result<(), OxirsError> {
78 match datatype_iri {
79 "http://www.w3.org/2001/XMLSchema#string"
81 | "http://www.w3.org/2001/XMLSchema#normalizedString"
82 | "http://www.w3.org/2001/XMLSchema#token" => {
83 Ok(())
85 }
86
87 "http://www.w3.org/2001/XMLSchema#boolean" => Boolean::from_str(value)
89 .map(|_| ())
90 .map_err(|e| OxirsError::Parse(format!("Invalid boolean value '{value}': {e}"))),
91
92 "http://www.w3.org/2001/XMLSchema#integer"
94 | "http://www.w3.org/2001/XMLSchema#long"
95 | "http://www.w3.org/2001/XMLSchema#int"
96 | "http://www.w3.org/2001/XMLSchema#short"
97 | "http://www.w3.org/2001/XMLSchema#byte"
98 | "http://www.w3.org/2001/XMLSchema#unsignedLong"
99 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
100 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
101 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
102 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
103 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
104 | "http://www.w3.org/2001/XMLSchema#negativeInteger"
105 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => Integer::from_str(value)
106 .map_err(|e| OxirsError::Parse(format!("Invalid integer value '{value}': {e}")))
107 .and_then(|integer| validate_integer_range_oxs(integer, datatype_iri)),
108
109 "http://www.w3.org/2001/XMLSchema#decimal" => Decimal::from_str(value)
111 .map(|_| ())
112 .map_err(|e| OxirsError::Parse(format!("Invalid decimal value '{value}': {e}"))),
113
114 "http://www.w3.org/2001/XMLSchema#float" => Float::from_str(value)
116 .map(|_| ())
117 .map_err(|e| OxirsError::Parse(format!("Invalid float value '{value}': {e}"))),
118 "http://www.w3.org/2001/XMLSchema#double" => Double::from_str(value)
119 .map(|_| ())
120 .map_err(|e| OxirsError::Parse(format!("Invalid double value '{value}': {e}"))),
121
122 "http://www.w3.org/2001/XMLSchema#dateTime" => DateTime::from_str(value)
124 .map(|_| ())
125 .map_err(|e| OxirsError::Parse(format!("Invalid dateTime value '{value}': {e}"))),
126
127 "http://www.w3.org/2001/XMLSchema#date" => Date::from_str(value)
128 .map(|_| ())
129 .map_err(|e| OxirsError::Parse(format!("Invalid date value '{value}': {e}"))),
130
131 "http://www.w3.org/2001/XMLSchema#time" => Time::from_str(value)
132 .map(|_| ())
133 .map_err(|e| OxirsError::Parse(format!("Invalid time value '{value}': {e}"))),
134
135 _ => Ok(()),
137 }
138}
139
140#[allow(dead_code)]
142fn validate_integer_range(value: &str, datatype_iri: &str) -> Result<(), OxirsError> {
143 let parsed_value: i64 = value
144 .parse()
145 .map_err(|_| OxirsError::Parse(format!("Cannot parse integer: '{value}'")))?;
146
147 match datatype_iri {
148 "http://www.w3.org/2001/XMLSchema#byte" if !(-128..=127).contains(&parsed_value) => {
149 return Err(OxirsError::Parse(format!(
150 "Byte value out of range: {parsed_value}. Must be between -128 and 127"
151 )));
152 }
153 "http://www.w3.org/2001/XMLSchema#short" if !(-32768..=32767).contains(&parsed_value) => {
154 return Err(OxirsError::Parse(format!(
155 "Short value out of range: {parsed_value}. Must be between -32768 and 32767"
156 )));
157 }
158 "http://www.w3.org/2001/XMLSchema#int"
159 if !(-2147483648..=2147483647).contains(&parsed_value) =>
160 {
161 return Err(OxirsError::Parse(format!(
162 "Int value out of range: {parsed_value}. Must be between -2147483648 and 2147483647"
163 )));
164 }
165 "http://www.w3.org/2001/XMLSchema#unsignedByte" if !(0..=255).contains(&parsed_value) => {
166 return Err(OxirsError::Parse(format!(
167 "Unsigned byte value out of range: {parsed_value}. Must be between 0 and 255"
168 )));
169 }
170 "http://www.w3.org/2001/XMLSchema#unsignedShort"
171 if !(0..=65535).contains(&parsed_value) =>
172 {
173 return Err(OxirsError::Parse(format!(
174 "Unsigned short value out of range: {parsed_value}. Must be between 0 and 65535"
175 )));
176 }
177 "http://www.w3.org/2001/XMLSchema#unsignedInt"
178 if !(0..=4294967295).contains(&parsed_value) =>
179 {
180 return Err(OxirsError::Parse(format!(
181 "Unsigned int value out of range: {parsed_value}. Must be between 0 and 4294967295"
182 )));
183 }
184 "http://www.w3.org/2001/XMLSchema#positiveInteger" if parsed_value <= 0 => {
185 return Err(OxirsError::Parse(format!(
186 "Positive integer must be greater than 0, got: {parsed_value}"
187 )));
188 }
189 "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" if parsed_value < 0 => {
190 return Err(OxirsError::Parse(format!(
191 "Non-negative integer must be >= 0, got: {parsed_value}"
192 )));
193 }
194 "http://www.w3.org/2001/XMLSchema#negativeInteger" if parsed_value >= 0 => {
195 return Err(OxirsError::Parse(format!(
196 "Negative integer must be less than 0, got: {parsed_value}"
197 )));
198 }
199 "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" if parsed_value > 0 => {
200 return Err(OxirsError::Parse(format!(
201 "Non-positive integer must be <= 0, got: {parsed_value}"
202 )));
203 }
204 _ => {} }
206
207 Ok(())
208}
209
210fn validate_integer_range_oxs(integer: Integer, datatype_iri: &str) -> Result<(), OxirsError> {
212 let parsed_value: i64 = integer.to_string().parse().map_err(|_| {
214 OxirsError::Parse("Cannot convert integer to i64 for range validation".to_string())
215 })?;
216
217 match datatype_iri {
218 "http://www.w3.org/2001/XMLSchema#byte" if !(-128..=127).contains(&parsed_value) => {
219 return Err(OxirsError::Parse(format!(
220 "Byte value out of range: {parsed_value}. Must be between -128 and 127"
221 )));
222 }
223 "http://www.w3.org/2001/XMLSchema#short" if !(-32768..=32767).contains(&parsed_value) => {
224 return Err(OxirsError::Parse(format!(
225 "Short value out of range: {parsed_value}. Must be between -32768 and 32767"
226 )));
227 }
228 "http://www.w3.org/2001/XMLSchema#int"
229 if !(-2147483648..=2147483647).contains(&parsed_value) =>
230 {
231 return Err(OxirsError::Parse(format!(
232 "Int value out of range: {parsed_value}. Must be between -2147483648 and 2147483647"
233 )));
234 }
235 "http://www.w3.org/2001/XMLSchema#unsignedByte" if !(0..=255).contains(&parsed_value) => {
236 return Err(OxirsError::Parse(format!(
237 "Unsigned byte value out of range: {parsed_value}. Must be between 0 and 255"
238 )));
239 }
240 "http://www.w3.org/2001/XMLSchema#unsignedShort"
241 if !(0..=65535).contains(&parsed_value) =>
242 {
243 return Err(OxirsError::Parse(format!(
244 "Unsigned short value out of range: {parsed_value}. Must be between 0 and 65535"
245 )));
246 }
247 "http://www.w3.org/2001/XMLSchema#unsignedInt"
248 if !(0..=4294967295).contains(&parsed_value) =>
249 {
250 return Err(OxirsError::Parse(format!(
251 "Unsigned int value out of range: {parsed_value}. Must be between 0 and 4294967295"
252 )));
253 }
254 "http://www.w3.org/2001/XMLSchema#positiveInteger" if parsed_value <= 0 => {
255 return Err(OxirsError::Parse(format!(
256 "Positive integer must be greater than 0, got: {parsed_value}"
257 )));
258 }
259 "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" if parsed_value < 0 => {
260 return Err(OxirsError::Parse(format!(
261 "Non-negative integer must be >= 0, got: {parsed_value}"
262 )));
263 }
264 "http://www.w3.org/2001/XMLSchema#negativeInteger" if parsed_value >= 0 => {
265 return Err(OxirsError::Parse(format!(
266 "Negative integer must be less than 0, got: {parsed_value}"
267 )));
268 }
269 "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" if parsed_value > 0 => {
270 return Err(OxirsError::Parse(format!(
271 "Non-positive integer must be <= 0, got: {parsed_value}"
272 )));
273 }
274 _ => {} }
276
277 Ok(())
278}
279
280#[derive(Eq, PartialEq, Debug, Clone, Hash, PartialOrd, Ord)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304pub struct Literal(LiteralContent);
305
306#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
307#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
308enum LiteralContent {
309 String(String),
310 LanguageTaggedString {
311 value: String,
312 language: String,
313 },
314 #[cfg(feature = "rdf-12")]
315 DirectionalLanguageTaggedString {
316 value: String,
317 language: String,
318 direction: BaseDirection,
319 },
320 TypedLiteral {
321 value: String,
322 datatype: NamedNode,
323 },
324}
325
326impl Literal {
327 #[inline]
329 pub fn new_simple_literal(value: impl Into<String>) -> Self {
330 Self(LiteralContent::String(value.into()))
331 }
332
333 #[inline]
335 pub fn new(value: impl Into<String>) -> Self {
336 Self::new_simple_literal(value)
337 }
338
339 #[inline]
341 pub fn new_typed_literal(value: impl Into<String>, datatype: impl Into<NamedNode>) -> Self {
342 let value = value.into();
343 let datatype = datatype.into();
344 Self(if datatype == *xsd::STRING {
345 LiteralContent::String(value)
346 } else {
347 LiteralContent::TypedLiteral { value, datatype }
348 })
349 }
350
351 #[inline]
353 pub fn new_typed(value: impl Into<String>, datatype: NamedNode) -> Self {
354 Self::new_typed_literal(value, datatype)
355 }
356
357 pub fn new_typed_validated(
359 value: impl Into<String>,
360 datatype: NamedNode,
361 ) -> Result<Self, OxirsError> {
362 let value = value.into();
363 validate_xsd_value(&value, datatype.as_str())?;
364 Ok(Literal::new_typed_literal(value, datatype))
365 }
366
367 #[inline]
369 pub fn new_language_tagged_literal(
370 value: impl Into<String>,
371 language: impl Into<String>,
372 ) -> Result<Self, LanguageTagParseError> {
373 let language = language.into().to_ascii_lowercase();
374 validate_language_tag(&language)?;
377 Ok(Self::new_language_tagged_literal_unchecked(value, language))
378 }
379
380 #[inline]
388 pub fn new_language_tagged_literal_unchecked(
389 value: impl Into<String>,
390 language: impl Into<String>,
391 ) -> Self {
392 Self(LiteralContent::LanguageTaggedString {
393 value: value.into(),
394 language: language.into(),
395 })
396 }
397
398 pub fn new_lang(
400 value: impl Into<String>,
401 language: impl Into<String>,
402 ) -> Result<Self, OxirsError> {
403 let result = Self::new_language_tagged_literal(value, language)?;
404 Ok(result)
405 }
406
407 #[cfg(feature = "rdf-12")]
409 #[inline]
410 pub fn new_directional_language_tagged_literal(
411 value: impl Into<String>,
412 language: impl Into<String>,
413 direction: impl Into<BaseDirection>,
414 ) -> Result<Self, LanguageTagParseError> {
415 let mut language = language.into();
416 language.make_ascii_lowercase();
417 validate_language_tag(&language)?;
418 Ok(Self::new_directional_language_tagged_literal_unchecked(
419 value, language, direction,
420 ))
421 }
422
423 #[cfg(feature = "rdf-12")]
431 #[inline]
432 pub fn new_directional_language_tagged_literal_unchecked(
433 value: impl Into<String>,
434 language: impl Into<String>,
435 direction: impl Into<BaseDirection>,
436 ) -> Self {
437 Self(LiteralContent::DirectionalLanguageTaggedString {
438 value: value.into(),
439 language: language.into(),
440 direction: direction.into(),
441 })
442 }
443
444 #[inline]
446 pub fn value(&self) -> &str {
447 self.as_ref().value()
448 }
449
450 #[inline]
455 pub fn language(&self) -> Option<&str> {
456 self.as_ref().language()
457 }
458
459 #[cfg(feature = "rdf-12")]
463 #[inline]
464 pub fn direction(&self) -> Option<BaseDirection> {
465 self.as_ref().direction()
466 }
467
468 #[inline]
473 pub fn datatype(&self) -> NamedNodeRef<'_> {
474 self.as_ref().datatype()
475 }
476
477 #[inline]
482 #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
483 pub fn is_plain(&self) -> bool {
484 #[allow(deprecated)]
485 self.as_ref().is_plain()
486 }
487
488 pub fn is_lang_string(&self) -> bool {
490 self.language().is_some()
491 }
492
493 pub fn is_typed(&self) -> bool {
495 matches!(&self.0, LiteralContent::TypedLiteral { .. })
496 }
497
498 #[inline]
499 pub fn as_ref(&self) -> LiteralRef<'_> {
500 LiteralRef(match &self.0 {
501 LiteralContent::String(value) => LiteralRefContent::String(value),
502 LiteralContent::LanguageTaggedString { value, language } => {
503 LiteralRefContent::LanguageTaggedString { value, language }
504 }
505 #[cfg(feature = "rdf-12")]
506 LiteralContent::DirectionalLanguageTaggedString {
507 value,
508 language,
509 direction,
510 } => LiteralRefContent::DirectionalLanguageTaggedString {
511 value,
512 language,
513 direction: *direction,
514 },
515 LiteralContent::TypedLiteral { value, datatype } => LiteralRefContent::TypedLiteral {
516 value,
517 datatype: NamedNodeRef::new_unchecked(datatype.as_str()),
518 },
519 })
520 }
521
522 #[inline]
524 pub fn destruct(self) -> (String, Option<NamedNode>, Option<String>) {
525 match self.0 {
526 LiteralContent::String(s) => (s, None, None),
527 LiteralContent::LanguageTaggedString { value, language } => {
528 (value, None, Some(language))
529 }
530 #[cfg(feature = "rdf-12")]
531 LiteralContent::DirectionalLanguageTaggedString {
532 value,
533 language,
534 direction: _,
535 } => (value, None, Some(language)),
536 LiteralContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
537 }
538 }
539
540 pub fn as_bool(&self) -> Option<bool> {
544 match self.value().to_lowercase().as_str() {
545 "true" | "1" => Some(true),
546 "false" | "0" => Some(false),
547 _ => None,
548 }
549 }
550
551 pub fn as_i64(&self) -> Option<i64> {
555 self.value().parse().ok()
556 }
557
558 pub fn as_i32(&self) -> Option<i32> {
560 self.value().parse().ok()
561 }
562
563 pub fn as_f64(&self) -> Option<f64> {
567 self.value().parse().ok()
568 }
569
570 pub fn as_f32(&self) -> Option<f32> {
572 self.value().parse().ok()
573 }
574
575 pub fn is_numeric(&self) -> bool {
577 match &self.0 {
578 LiteralContent::TypedLiteral { datatype, .. } => {
579 let dt_iri = datatype.as_str();
580 matches!(
581 dt_iri,
582 "http://www.w3.org/2001/XMLSchema#integer"
583 | "http://www.w3.org/2001/XMLSchema#decimal"
584 | "http://www.w3.org/2001/XMLSchema#double"
585 | "http://www.w3.org/2001/XMLSchema#float"
586 | "http://www.w3.org/2001/XMLSchema#long"
587 | "http://www.w3.org/2001/XMLSchema#int"
588 | "http://www.w3.org/2001/XMLSchema#short"
589 | "http://www.w3.org/2001/XMLSchema#byte"
590 | "http://www.w3.org/2001/XMLSchema#unsignedLong"
591 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
592 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
593 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
594 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
595 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
596 | "http://www.w3.org/2001/XMLSchema#negativeInteger"
597 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger"
598 )
599 }
600 _ => {
601 self.as_f64().is_some()
603 }
604 }
605 }
606
607 pub fn is_boolean(&self) -> bool {
609 match &self.0 {
610 LiteralContent::TypedLiteral { datatype, .. } => {
611 datatype.as_str() == "http://www.w3.org/2001/XMLSchema#boolean"
612 }
613 _ => self.as_bool().is_some(),
614 }
615 }
616
617 pub fn canonical_form(&self) -> Literal {
621 match &self.0 {
622 LiteralContent::TypedLiteral { value, datatype } => {
623 let dt_iri = datatype.as_str();
624 match dt_iri {
625 "http://www.w3.org/2001/XMLSchema#boolean" => {
626 if let Some(bool_val) = self.as_bool() {
627 let canonical_value = if bool_val { "true" } else { "false" };
628 return Literal::new_typed(canonical_value, datatype.clone());
629 }
630 }
631 "http://www.w3.org/2001/XMLSchema#integer"
632 | "http://www.w3.org/2001/XMLSchema#long"
633 | "http://www.w3.org/2001/XMLSchema#int"
634 | "http://www.w3.org/2001/XMLSchema#short"
635 | "http://www.w3.org/2001/XMLSchema#byte" => {
636 if let Some(int_val) = self.as_i64() {
637 return Literal::new_typed(int_val.to_string(), datatype.clone());
638 }
639 }
640 "http://www.w3.org/2001/XMLSchema#unsignedLong"
641 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
642 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
643 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
644 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
645 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => {
646 if let Some(int_val) = self.as_i64() {
647 if int_val >= 0 {
648 return Literal::new_typed(int_val.to_string(), datatype.clone());
649 }
650 }
651 }
652 "http://www.w3.org/2001/XMLSchema#negativeInteger"
653 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => {
654 if let Some(int_val) = self.as_i64() {
655 if int_val <= 0 {
656 return Literal::new_typed(int_val.to_string(), datatype.clone());
657 }
658 }
659 }
660 "http://www.w3.org/2001/XMLSchema#decimal" => {
661 if let Some(dec_val) = self.as_f64() {
662 let formatted = format!("{dec_val}");
664 if formatted.contains('.') {
665 let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
666 return Literal::new_typed(
667 if trimmed.is_empty() || trimmed == "-" {
668 "0"
669 } else {
670 trimmed
671 },
672 datatype.clone(),
673 );
674 } else {
675 return Literal::new_typed(
676 format!("{formatted}.0"),
677 datatype.clone(),
678 );
679 }
680 }
681 }
682 "http://www.w3.org/2001/XMLSchema#double"
683 | "http://www.w3.org/2001/XMLSchema#float" => {
684 if let Some(float_val) = self.as_f64() {
685 if float_val.is_infinite() {
687 return Literal::new_typed(
688 if float_val.is_sign_positive() {
689 "INF"
690 } else {
691 "-INF"
692 },
693 datatype.clone(),
694 );
695 } else if float_val.is_nan() {
696 return Literal::new_typed("NaN", datatype.clone());
697 } else {
698 let formatted = if float_val.abs() >= 1e6
700 || (float_val.abs() < 1e-3 && float_val != 0.0)
701 {
702 format!("{float_val:E}")
703 } else {
704 format!("{float_val}")
705 };
706 return Literal::new_typed(formatted, datatype.clone());
707 }
708 }
709 }
710 "http://www.w3.org/2001/XMLSchema#normalizedString" => {
711 let normalized = value.replace(['\t', '\n', '\r'], " ");
713 return Literal::new_typed(normalized, datatype.clone());
714 }
715 "http://www.w3.org/2001/XMLSchema#string" => {
716 }
718 "http://www.w3.org/2001/XMLSchema#token" => {
719 let normalized = value.split_whitespace().collect::<Vec<_>>().join(" ");
721 return Literal::new_typed(normalized, datatype.clone());
722 }
723 _ => {}
724 }
725 }
726 LiteralContent::LanguageTaggedString { value, language } => {
727 return Self(LiteralContent::LanguageTaggedString {
729 value: value.clone(),
730 language: language.clone(),
731 });
732 }
733 _ => {}
734 }
735 self.clone()
736 }
737
738 pub fn validate(&self) -> Result<(), OxirsError> {
740 match &self.0 {
741 LiteralContent::String(_) => Ok(()),
742 LiteralContent::LanguageTaggedString { language, .. } => {
743 validate_language_tag(language).map_err(Into::into)
744 }
745 #[cfg(feature = "rdf-12")]
746 LiteralContent::DirectionalLanguageTaggedString { language, .. } => {
747 validate_language_tag(language).map_err(Into::into)
748 }
749 LiteralContent::TypedLiteral { value, datatype } => {
750 validate_xsd_value(value, datatype.as_str())
751 }
752 }
753 }
754}
755
756impl fmt::Display for Literal {
757 #[inline]
758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759 self.as_ref().fmt(f)
760 }
761}
762
763impl RdfTerm for Literal {
764 fn as_str(&self) -> &str {
765 self.value()
766 }
767
768 fn is_literal(&self) -> bool {
769 true
770 }
771}
772
773impl ObjectTerm for Literal {}
774
775#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
793pub struct LiteralRef<'a>(LiteralRefContent<'a>);
794
795#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
796enum LiteralRefContent<'a> {
797 String(&'a str),
798 LanguageTaggedString {
799 value: &'a str,
800 language: &'a str,
801 },
802 #[cfg(feature = "rdf-12")]
803 DirectionalLanguageTaggedString {
804 value: &'a str,
805 language: &'a str,
806 direction: BaseDirection,
807 },
808 TypedLiteral {
809 value: &'a str,
810 datatype: NamedNodeRef<'a>,
811 },
812}
813
814impl<'a> LiteralRef<'a> {
815 #[inline]
817 pub const fn new_simple_literal(value: &'a str) -> Self {
818 LiteralRef(LiteralRefContent::String(value))
819 }
820
821 #[inline]
823 pub const fn new(value: &'a str) -> Self {
824 Self::new_simple_literal(value)
825 }
826
827 #[inline]
829 pub fn new_typed_literal(value: &'a str, datatype: impl Into<NamedNodeRef<'a>>) -> Self {
830 let datatype = datatype.into();
831 LiteralRef(if datatype == xsd::STRING.as_ref() {
832 LiteralRefContent::String(value)
833 } else {
834 LiteralRefContent::TypedLiteral { value, datatype }
835 })
836 }
837
838 #[inline]
840 pub fn new_typed(value: &'a str, datatype: NamedNodeRef<'a>) -> Self {
841 Self::new_typed_literal(value, datatype)
842 }
843
844 #[inline]
852 pub const fn new_language_tagged_literal_unchecked(value: &'a str, language: &'a str) -> Self {
853 LiteralRef(LiteralRefContent::LanguageTaggedString { value, language })
854 }
855
856 #[inline]
858 pub const fn new_lang(value: &'a str, language: &'a str) -> Self {
859 Self::new_language_tagged_literal_unchecked(value, language)
860 }
861
862 #[cfg(feature = "rdf-12")]
870 #[inline]
871 pub const fn new_directional_language_tagged_literal_unchecked(
872 value: &'a str,
873 language: &'a str,
874 direction: BaseDirection,
875 ) -> Self {
876 LiteralRef(LiteralRefContent::DirectionalLanguageTaggedString {
877 value,
878 language,
879 direction,
880 })
881 }
882
883 #[inline]
885 pub const fn value(self) -> &'a str {
886 match self.0 {
887 LiteralRefContent::String(value)
888 | LiteralRefContent::LanguageTaggedString { value, .. }
889 | LiteralRefContent::TypedLiteral { value, .. } => value,
890 #[cfg(feature = "rdf-12")]
891 LiteralRefContent::DirectionalLanguageTaggedString { value, .. } => value,
892 }
893 }
894
895 #[inline]
900 pub const fn language(self) -> Option<&'a str> {
901 match self.0 {
902 LiteralRefContent::LanguageTaggedString { language, .. } => Some(language),
903 #[cfg(feature = "rdf-12")]
904 LiteralRefContent::DirectionalLanguageTaggedString { language, .. } => Some(language),
905 _ => None,
906 }
907 }
908
909 #[cfg(feature = "rdf-12")]
913 #[inline]
914 pub const fn direction(self) -> Option<BaseDirection> {
915 match self.0 {
916 LiteralRefContent::DirectionalLanguageTaggedString { direction, .. } => Some(direction),
917 _ => None,
918 }
919 }
920
921 #[inline]
926 pub fn datatype(self) -> NamedNodeRef<'a> {
927 match self.0 {
928 LiteralRefContent::String(_) => xsd::STRING.as_ref(),
929 LiteralRefContent::LanguageTaggedString { .. } => rdf::LANG_STRING.as_ref(),
930 #[cfg(feature = "rdf-12")]
931 LiteralRefContent::DirectionalLanguageTaggedString { .. } => {
932 rdf::DIR_LANG_STRING.as_ref()
933 }
934 LiteralRefContent::TypedLiteral { datatype, .. } => datatype,
935 }
936 }
937
938 #[inline]
943 #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
944 pub const fn is_plain(self) -> bool {
945 matches!(
946 self.0,
947 LiteralRefContent::String(_) | LiteralRefContent::LanguageTaggedString { .. }
948 )
949 }
950
951 #[inline]
952 pub fn into_owned(self) -> Literal {
953 Literal(match self.0 {
954 LiteralRefContent::String(value) => LiteralContent::String(value.to_owned()),
955 LiteralRefContent::LanguageTaggedString { value, language } => {
956 LiteralContent::LanguageTaggedString {
957 value: value.to_owned(),
958 language: language.to_owned(),
959 }
960 }
961 #[cfg(feature = "rdf-12")]
962 LiteralRefContent::DirectionalLanguageTaggedString {
963 value,
964 language,
965 direction,
966 } => LiteralContent::DirectionalLanguageTaggedString {
967 value: value.to_owned(),
968 language: language.to_owned(),
969 direction,
970 },
971 LiteralRefContent::TypedLiteral { value, datatype } => LiteralContent::TypedLiteral {
972 value: value.to_owned(),
973 datatype: datatype.into_owned(),
974 },
975 })
976 }
977
978 #[inline]
980 pub fn to_owned(&self) -> Literal {
981 self.into_owned()
982 }
983}
984
985impl fmt::Display for LiteralRef<'_> {
986 #[inline]
987 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
988 match self.0 {
989 LiteralRefContent::String(value) => print_quoted_str(value, f),
990 LiteralRefContent::LanguageTaggedString { value, language } => {
991 print_quoted_str(value, f)?;
992 write!(f, "@{language}")
993 }
994 #[cfg(feature = "rdf-12")]
995 LiteralRefContent::DirectionalLanguageTaggedString {
996 value,
997 language,
998 direction,
999 } => {
1000 print_quoted_str(value, f)?;
1001 write!(f, "@{language}--{direction}")
1002 }
1003 LiteralRefContent::TypedLiteral { value, datatype } => {
1004 print_quoted_str(value, f)?;
1005 write!(f, "^^{datatype}")
1006 }
1007 }
1008 }
1009}
1010
1011impl<'a> RdfTerm for LiteralRef<'a> {
1012 fn as_str(&self) -> &str {
1013 self.value()
1014 }
1015
1016 fn is_literal(&self) -> bool {
1017 true
1018 }
1019}
1020
1021#[inline]
1023pub fn print_quoted_str(string: &str, f: &mut impl Write) -> fmt::Result {
1024 f.write_char('"')?;
1025 for c in string.chars() {
1026 match c {
1027 '\u{08}' => f.write_str("\\b"),
1028 '\t' => f.write_str("\\t"),
1029 '\n' => f.write_str("\\n"),
1030 '\u{0C}' => f.write_str("\\f"),
1031 '\r' => f.write_str("\\r"),
1032 '"' => f.write_str("\\\""),
1033 '\\' => f.write_str("\\\\"),
1034 '\0'..='\u{1F}' | '\u{7F}' => write!(f, "\\u{:04X}", u32::from(c)),
1035 _ => f.write_char(c),
1036 }?;
1037 }
1038 f.write_char('"')
1039}
1040
1041#[cfg(feature = "rdf-12")]
1043#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash, PartialOrd, Ord)]
1044#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1045pub enum BaseDirection {
1046 Ltr,
1048 Rtl,
1050}
1051
1052#[cfg(feature = "rdf-12")]
1053impl fmt::Display for BaseDirection {
1054 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1055 f.write_str(match self {
1056 Self::Ltr => "ltr",
1057 Self::Rtl => "rtl",
1058 })
1059 }
1060}
1061
1062impl<'a> From<&'a Literal> for LiteralRef<'a> {
1063 #[inline]
1064 fn from(node: &'a Literal) -> Self {
1065 node.as_ref()
1066 }
1067}
1068
1069impl<'a> From<LiteralRef<'a>> for Literal {
1070 #[inline]
1071 fn from(node: LiteralRef<'a>) -> Self {
1072 node.into_owned()
1073 }
1074}
1075
1076impl<'a> From<&'a str> for LiteralRef<'a> {
1077 #[inline]
1078 fn from(value: &'a str) -> Self {
1079 LiteralRef(LiteralRefContent::String(value))
1080 }
1081}
1082
1083impl PartialEq<Literal> for LiteralRef<'_> {
1084 #[inline]
1085 fn eq(&self, other: &Literal) -> bool {
1086 *self == other.as_ref()
1087 }
1088}
1089
1090impl PartialEq<LiteralRef<'_>> for Literal {
1091 #[inline]
1092 fn eq(&self, other: &LiteralRef<'_>) -> bool {
1093 self.as_ref() == *other
1094 }
1095}
1096
1097impl<'a> From<&'a str> for Literal {
1099 #[inline]
1100 fn from(value: &'a str) -> Self {
1101 Self(LiteralContent::String(value.into()))
1102 }
1103}
1104
1105impl From<String> for Literal {
1106 #[inline]
1107 fn from(value: String) -> Self {
1108 Self(LiteralContent::String(value))
1109 }
1110}
1111
1112impl<'a> From<Cow<'a, str>> for Literal {
1113 #[inline]
1114 fn from(value: Cow<'a, str>) -> Self {
1115 Self(LiteralContent::String(value.into()))
1116 }
1117}
1118
1119impl From<bool> for Literal {
1120 #[inline]
1121 fn from(value: bool) -> Self {
1122 Self(LiteralContent::TypedLiteral {
1123 value: value.to_string(),
1124 datatype: xsd::BOOLEAN.clone(),
1125 })
1126 }
1127}
1128
1129impl From<i128> for Literal {
1130 #[inline]
1131 fn from(value: i128) -> Self {
1132 Self(LiteralContent::TypedLiteral {
1133 value: value.to_string(),
1134 datatype: xsd::INTEGER.clone(),
1135 })
1136 }
1137}
1138
1139impl From<i64> for Literal {
1140 #[inline]
1141 fn from(value: i64) -> Self {
1142 Self(LiteralContent::TypedLiteral {
1143 value: value.to_string(),
1144 datatype: xsd::INTEGER.clone(),
1145 })
1146 }
1147}
1148
1149impl From<i32> for Literal {
1150 #[inline]
1151 fn from(value: i32) -> Self {
1152 Self(LiteralContent::TypedLiteral {
1153 value: value.to_string(),
1154 datatype: xsd::INTEGER.clone(),
1155 })
1156 }
1157}
1158
1159impl From<i16> for Literal {
1160 #[inline]
1161 fn from(value: i16) -> Self {
1162 Self(LiteralContent::TypedLiteral {
1163 value: value.to_string(),
1164 datatype: xsd::INTEGER.clone(),
1165 })
1166 }
1167}
1168
1169impl From<u64> for Literal {
1170 #[inline]
1171 fn from(value: u64) -> Self {
1172 Self(LiteralContent::TypedLiteral {
1173 value: value.to_string(),
1174 datatype: xsd::INTEGER.clone(),
1175 })
1176 }
1177}
1178
1179impl From<u32> for Literal {
1180 #[inline]
1181 fn from(value: u32) -> Self {
1182 Self(LiteralContent::TypedLiteral {
1183 value: value.to_string(),
1184 datatype: xsd::INTEGER.clone(),
1185 })
1186 }
1187}
1188
1189impl From<u16> for Literal {
1190 #[inline]
1191 fn from(value: u16) -> Self {
1192 Self(LiteralContent::TypedLiteral {
1193 value: value.to_string(),
1194 datatype: xsd::INTEGER.clone(),
1195 })
1196 }
1197}
1198
1199impl From<f32> for Literal {
1200 #[inline]
1201 fn from(value: f32) -> Self {
1202 Self(LiteralContent::TypedLiteral {
1203 value: if value == f32::INFINITY {
1204 "INF".to_owned()
1205 } else if value == f32::NEG_INFINITY {
1206 "-INF".to_owned()
1207 } else {
1208 value.to_string()
1209 },
1210 datatype: xsd::FLOAT.clone(),
1211 })
1212 }
1213}
1214
1215impl From<f64> for Literal {
1216 #[inline]
1217 fn from(value: f64) -> Self {
1218 Self(LiteralContent::TypedLiteral {
1219 value: if value == f64::INFINITY {
1220 "INF".to_owned()
1221 } else if value == f64::NEG_INFINITY {
1222 "-INF".to_owned()
1223 } else {
1224 value.to_string()
1225 },
1226 datatype: xsd::DOUBLE.clone(),
1227 })
1228 }
1229}
1230
1231pub mod xsd_literals {
1233 use super::*;
1234 use crate::vocab::xsd;
1235
1236 pub fn boolean_literal(value: bool) -> Literal {
1240 Literal::new_typed(value.to_string(), xsd::BOOLEAN.clone())
1241 }
1242
1243 pub fn integer_literal(value: i64) -> Literal {
1245 Literal::new_typed(value.to_string(), xsd::INTEGER.clone())
1246 }
1247
1248 pub fn decimal_literal(value: f64) -> Literal {
1250 Literal::new_typed(value.to_string(), xsd::DECIMAL.clone())
1251 }
1252
1253 pub fn double_literal(value: f64) -> Literal {
1255 Literal::new_typed(value.to_string(), xsd::DOUBLE.clone())
1256 }
1257
1258 pub fn string_literal(value: &str) -> Literal {
1260 Literal::new_typed(value, xsd::STRING.clone())
1261 }
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266 use super::*;
1267
1268 #[test]
1269 fn test_simple_literal_equality() {
1270 assert_eq!(
1271 Literal::new_simple_literal("foo"),
1272 Literal::new_typed_literal("foo", xsd::STRING.clone())
1273 );
1274 assert_eq!(
1275 Literal::new_simple_literal("foo"),
1276 LiteralRef::new_typed_literal("foo", xsd::STRING.as_ref())
1277 );
1278 assert_eq!(
1279 LiteralRef::new_simple_literal("foo"),
1280 Literal::new_typed_literal("foo", xsd::STRING.clone())
1281 );
1282 assert_eq!(
1283 LiteralRef::new_simple_literal("foo"),
1284 LiteralRef::new_typed_literal("foo", xsd::STRING.as_ref())
1285 );
1286 }
1287
1288 #[test]
1289 fn test_float_format() {
1290 assert_eq!("INF", Literal::from(f32::INFINITY).value());
1291 assert_eq!("INF", Literal::from(f64::INFINITY).value());
1292 assert_eq!("-INF", Literal::from(f32::NEG_INFINITY).value());
1293 assert_eq!("-INF", Literal::from(f64::NEG_INFINITY).value());
1294 assert_eq!("NaN", Literal::from(f32::NAN).value());
1295 assert_eq!("NaN", Literal::from(f64::NAN).value());
1296 }
1297
1298 #[test]
1299 fn test_plain_literal() {
1300 let literal = Literal::new("Hello");
1301 assert_eq!(literal.value(), "Hello");
1302 #[allow(deprecated)]
1303 {
1304 assert!(literal.is_plain());
1305 }
1306 assert!(!literal.is_lang_string());
1307 assert!(!literal.is_typed());
1308 assert_eq!(format!("{literal}"), "\"Hello\"");
1309 }
1310
1311 #[test]
1312 fn test_lang_literal() {
1313 let literal = Literal::new_lang("Hello", "en").expect("construction should succeed");
1314 assert_eq!(literal.value(), "Hello");
1315 assert_eq!(literal.language(), Some("en"));
1316 #[allow(deprecated)]
1317 {
1318 assert!(literal.is_plain());
1319 }
1320 assert!(literal.is_lang_string());
1321 assert!(!literal.is_typed());
1322 assert_eq!(format!("{literal}"), "\"Hello\"@en");
1323 }
1324
1325 #[test]
1326 fn test_typed_literal() {
1327 let literal = Literal::new_typed("42", xsd::INTEGER.clone());
1328 assert_eq!(literal.value(), "42");
1329 assert_eq!(
1330 literal.datatype().as_str(),
1331 "http://www.w3.org/2001/XMLSchema#integer"
1332 );
1333 #[allow(deprecated)]
1334 {
1335 assert!(!literal.is_plain());
1336 }
1337 assert!(!literal.is_lang_string());
1338 assert!(literal.is_typed());
1339 assert_eq!(
1340 format!("{literal}"),
1341 "\"42\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1342 );
1343 }
1344
1345 #[test]
1346 fn test_literal_ref() {
1347 let literal_ref = LiteralRef::new("test");
1348 assert_eq!(literal_ref.value(), "test");
1349
1350 let owned = literal_ref.to_owned();
1351 assert_eq!(owned.value(), "test");
1352 }
1353
1354 #[test]
1355 fn test_boolean_extraction() {
1356 let bool_literal = xsd_literals::boolean_literal(true);
1357 assert!(bool_literal.is_boolean());
1358 assert_eq!(bool_literal.as_bool(), Some(true));
1359
1360 let false_literal = Literal::new_typed("false", xsd::BOOLEAN.clone());
1361 assert_eq!(false_literal.as_bool(), Some(false));
1362
1363 let true_str = Literal::new("true");
1365 assert_eq!(true_str.as_bool(), Some(true));
1366
1367 let false_str = Literal::new("0");
1368 assert_eq!(false_str.as_bool(), Some(false));
1369 }
1370
1371 #[test]
1372 fn test_numeric_extraction() {
1373 let int_literal = xsd_literals::integer_literal(42);
1374 assert!(int_literal.is_numeric());
1375 assert_eq!(int_literal.as_i64(), Some(42));
1376 assert_eq!(int_literal.as_i32(), Some(42));
1377 assert_eq!(int_literal.as_f64(), Some(42.0));
1378
1379 let decimal_literal = xsd_literals::decimal_literal(3.25);
1380 assert!(decimal_literal.is_numeric());
1381 assert_eq!(decimal_literal.as_f64(), Some(3.25));
1382 assert_eq!(decimal_literal.as_f32(), Some(3.25_f32));
1383
1384 let untyped_num = Literal::new("123");
1386 assert!(untyped_num.is_numeric());
1387 assert_eq!(untyped_num.as_i64(), Some(123));
1388 }
1389
1390 #[test]
1391 fn test_canonical_form() {
1392 let bool_literal = Literal::new_typed("True", xsd::BOOLEAN.clone());
1394 let canonical = bool_literal.canonical_form();
1395 assert_eq!(canonical.value(), "true");
1396
1397 let int_literal = Literal::new_typed(" 42 ", xsd::INTEGER.clone());
1399 let canonical = int_literal.canonical_form();
1402 assert_eq!(
1403 canonical.datatype().as_str(),
1404 "http://www.w3.org/2001/XMLSchema#integer"
1405 );
1406
1407 let dec_literal = Literal::new_typed("3.140", xsd::DECIMAL.clone());
1409 let canonical = dec_literal.canonical_form();
1410 assert_eq!(canonical.value(), "3.14"); }
1412
1413 #[test]
1414 fn test_xsd_convenience_functions() {
1415 assert_eq!(xsd_literals::boolean_literal(true).value(), "true");
1417 assert_eq!(xsd_literals::integer_literal(123).value(), "123");
1418 assert_eq!(xsd_literals::decimal_literal(3.25).value(), "3.25");
1419 assert_eq!(xsd_literals::double_literal(2.71).value(), "2.71");
1420 assert_eq!(xsd_literals::string_literal("hello").value(), "hello");
1421
1422 assert_eq!(
1424 xsd_literals::boolean_literal(true).datatype().as_str(),
1425 "http://www.w3.org/2001/XMLSchema#boolean"
1426 );
1427 assert_eq!(
1428 xsd_literals::integer_literal(123).datatype().as_str(),
1429 "http://www.w3.org/2001/XMLSchema#integer"
1430 );
1431 }
1432
1433 #[test]
1434 fn test_numeric_type_detection() {
1435 let int_lit = Literal::new_typed("42", xsd::INTEGER.clone());
1437 assert!(int_lit.is_numeric());
1438
1439 let float_lit = Literal::new_typed("3.14", xsd::FLOAT.clone());
1440 assert!(float_lit.is_numeric());
1441
1442 let double_lit = Literal::new_typed("2.71", xsd::DOUBLE.clone());
1443 assert!(double_lit.is_numeric());
1444
1445 let string_lit = Literal::new_typed("hello", xsd::STRING.clone());
1447 assert!(!string_lit.is_numeric());
1448
1449 let bool_lit = Literal::new_typed("true", xsd::BOOLEAN.clone());
1450 assert!(!bool_lit.is_numeric());
1451 }
1452}