1use crate::model::{NamedNode, NamedNodeRef, ObjectTerm, RdfTerm};
7use crate::vocab::{rdf, xsd};
8use crate::OxirsError;
9use lazy_static::lazy_static;
10use oxilangtag::LanguageTag as OxiLanguageTag;
11use oxsdatatypes::{Boolean, Date, DateTime, Decimal, Double, Float, Integer, Time};
12use regex::Regex;
13use std::borrow::Cow;
14use std::fmt::{self, Write};
15use std::hash::Hash;
16use std::str::FromStr;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct LanguageTagParseError {
21 message: String,
22}
23
24impl fmt::Display for LanguageTagParseError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 write!(f, "Language tag parse error: {}", self.message)
27 }
28}
29
30impl std::error::Error for LanguageTagParseError {}
31
32impl From<LanguageTagParseError> for OxirsError {
33 fn from(err: LanguageTagParseError) -> Self {
34 OxirsError::Parse(err.message)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct LanguageTag {
41 tag: String,
42}
43
44impl LanguageTag {
45 pub fn parse(tag: impl Into<String>) -> Result<Self, LanguageTagParseError> {
47 let tag = tag.into();
48 validate_language_tag(&tag)?;
49 Ok(LanguageTag { tag })
50 }
51
52 pub fn as_str(&self) -> &str {
54 &self.tag
55 }
56
57 pub fn into_inner(self) -> String {
59 self.tag
60 }
61}
62
63impl fmt::Display for LanguageTag {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str(&self.tag)
66 }
67}
68
69lazy_static! {
70 static ref LANGUAGE_TAG_REGEX: Regex = Regex::new(
73 r"^([a-zA-Z]{2,3}(-[a-zA-Z]{3}){0,3}(-[a-zA-Z]{4})?(-[a-zA-Z]{2}|\d{3})?(-[0-9a-zA-Z]{5,8}|-\d[0-9a-zA-Z]{3})*(-[0-9a-wyzA-WYZ](-[0-9a-zA-Z]{2,8})+)*(-x(-[0-9a-zA-Z]{1,8})+)?|x(-[0-9a-zA-Z]{1,8})+|[a-zA-Z]{4}|[a-zA-Z]{5,8})$"
74 ).expect("Language tag regex compilation failed");
75
76 static ref SIMPLE_LANGUAGE_REGEX: Regex = Regex::new(
78 r"^[a-zA-Z]{2,3}$"
79 ).expect("Simple language regex compilation failed");
80
81 static ref INTEGER_REGEX: Regex = Regex::new(
83 r"^[+-]?\d+$"
84 ).expect("Integer regex compilation failed");
85
86 static ref DECIMAL_REGEX: Regex = Regex::new(
87 r"^[+-]?(\d+(\.\d*)?|\.\d+)$"
88 ).expect("Decimal regex compilation failed");
89
90 static ref DOUBLE_REGEX: Regex = Regex::new(
91 r"^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$|^[+-]?INF$|^NaN$"
92 ).expect("Double regex compilation failed");
93
94 static ref BOOLEAN_REGEX: Regex = Regex::new(
95 r"^(true|false|1|0)$"
96 ).expect("Boolean regex compilation failed");
97
98 static ref DATETIME_REGEX: Regex = Regex::new(
100 r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$"
101 ).expect("DateTime regex compilation failed");
102
103 static ref DATE_REGEX: Regex = Regex::new(
104 r"^\d{4}-\d{2}-\d{2}(Z|[+-]\d{2}:\d{2})?$"
105 ).expect("Date regex compilation failed");
106
107 static ref TIME_REGEX: Regex = Regex::new(
108 r"^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$"
109 ).expect("Time regex compilation failed");
110}
111
112fn validate_language_tag(tag: &str) -> Result<(), LanguageTagParseError> {
114 OxiLanguageTag::parse(tag)
115 .map(|_| ())
116 .map_err(|e| LanguageTagParseError {
117 message: format!("Invalid language tag '{tag}': {e}"),
118 })
119}
120
121pub fn validate_xsd_value(value: &str, datatype_iri: &str) -> Result<(), OxirsError> {
123 match datatype_iri {
124 "http://www.w3.org/2001/XMLSchema#string"
126 | "http://www.w3.org/2001/XMLSchema#normalizedString"
127 | "http://www.w3.org/2001/XMLSchema#token" => {
128 Ok(())
130 }
131
132 "http://www.w3.org/2001/XMLSchema#boolean" => Boolean::from_str(value)
134 .map(|_| ())
135 .map_err(|e| OxirsError::Parse(format!("Invalid boolean value '{value}': {e}"))),
136
137 "http://www.w3.org/2001/XMLSchema#integer"
139 | "http://www.w3.org/2001/XMLSchema#long"
140 | "http://www.w3.org/2001/XMLSchema#int"
141 | "http://www.w3.org/2001/XMLSchema#short"
142 | "http://www.w3.org/2001/XMLSchema#byte"
143 | "http://www.w3.org/2001/XMLSchema#unsignedLong"
144 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
145 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
146 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
147 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
148 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
149 | "http://www.w3.org/2001/XMLSchema#negativeInteger"
150 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => Integer::from_str(value)
151 .map_err(|e| OxirsError::Parse(format!("Invalid integer value '{value}': {e}")))
152 .and_then(|integer| validate_integer_range_oxs(integer, datatype_iri)),
153
154 "http://www.w3.org/2001/XMLSchema#decimal" => Decimal::from_str(value)
156 .map(|_| ())
157 .map_err(|e| OxirsError::Parse(format!("Invalid decimal value '{value}': {e}"))),
158
159 "http://www.w3.org/2001/XMLSchema#float" => Float::from_str(value)
161 .map(|_| ())
162 .map_err(|e| OxirsError::Parse(format!("Invalid float value '{value}': {e}"))),
163 "http://www.w3.org/2001/XMLSchema#double" => Double::from_str(value)
164 .map(|_| ())
165 .map_err(|e| OxirsError::Parse(format!("Invalid double value '{value}': {e}"))),
166
167 "http://www.w3.org/2001/XMLSchema#dateTime" => DateTime::from_str(value)
169 .map(|_| ())
170 .map_err(|e| OxirsError::Parse(format!("Invalid dateTime value '{value}': {e}"))),
171
172 "http://www.w3.org/2001/XMLSchema#date" => Date::from_str(value)
173 .map(|_| ())
174 .map_err(|e| OxirsError::Parse(format!("Invalid date value '{value}': {e}"))),
175
176 "http://www.w3.org/2001/XMLSchema#time" => Time::from_str(value)
177 .map(|_| ())
178 .map_err(|e| OxirsError::Parse(format!("Invalid time value '{value}': {e}"))),
179
180 _ => Ok(()),
182 }
183}
184
185#[allow(dead_code)]
187fn validate_integer_range(value: &str, datatype_iri: &str) -> Result<(), OxirsError> {
188 let parsed_value: i64 = value
189 .parse()
190 .map_err(|_| OxirsError::Parse(format!("Cannot parse integer: '{value}'")))?;
191
192 match datatype_iri {
193 "http://www.w3.org/2001/XMLSchema#byte" if !(-128..=127).contains(&parsed_value) => {
194 return Err(OxirsError::Parse(format!(
195 "Byte value out of range: {parsed_value}. Must be between -128 and 127"
196 )));
197 }
198 "http://www.w3.org/2001/XMLSchema#short" if !(-32768..=32767).contains(&parsed_value) => {
199 return Err(OxirsError::Parse(format!(
200 "Short value out of range: {parsed_value}. Must be between -32768 and 32767"
201 )));
202 }
203 "http://www.w3.org/2001/XMLSchema#int"
204 if !(-2147483648..=2147483647).contains(&parsed_value) =>
205 {
206 return Err(OxirsError::Parse(format!(
207 "Int value out of range: {parsed_value}. Must be between -2147483648 and 2147483647"
208 )));
209 }
210 "http://www.w3.org/2001/XMLSchema#unsignedByte" if !(0..=255).contains(&parsed_value) => {
211 return Err(OxirsError::Parse(format!(
212 "Unsigned byte value out of range: {parsed_value}. Must be between 0 and 255"
213 )));
214 }
215 "http://www.w3.org/2001/XMLSchema#unsignedShort"
216 if !(0..=65535).contains(&parsed_value) =>
217 {
218 return Err(OxirsError::Parse(format!(
219 "Unsigned short value out of range: {parsed_value}. Must be between 0 and 65535"
220 )));
221 }
222 "http://www.w3.org/2001/XMLSchema#unsignedInt"
223 if !(0..=4294967295).contains(&parsed_value) =>
224 {
225 return Err(OxirsError::Parse(format!(
226 "Unsigned int value out of range: {parsed_value}. Must be between 0 and 4294967295"
227 )));
228 }
229 "http://www.w3.org/2001/XMLSchema#positiveInteger" if parsed_value <= 0 => {
230 return Err(OxirsError::Parse(format!(
231 "Positive integer must be greater than 0, got: {parsed_value}"
232 )));
233 }
234 "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" if parsed_value < 0 => {
235 return Err(OxirsError::Parse(format!(
236 "Non-negative integer must be >= 0, got: {parsed_value}"
237 )));
238 }
239 "http://www.w3.org/2001/XMLSchema#negativeInteger" if parsed_value >= 0 => {
240 return Err(OxirsError::Parse(format!(
241 "Negative integer must be less than 0, got: {parsed_value}"
242 )));
243 }
244 "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" if parsed_value > 0 => {
245 return Err(OxirsError::Parse(format!(
246 "Non-positive integer must be <= 0, got: {parsed_value}"
247 )));
248 }
249 _ => {} }
251
252 Ok(())
253}
254
255fn validate_integer_range_oxs(integer: Integer, datatype_iri: &str) -> Result<(), OxirsError> {
257 let parsed_value: i64 = integer.to_string().parse().map_err(|_| {
259 OxirsError::Parse("Cannot convert integer to i64 for range validation".to_string())
260 })?;
261
262 match datatype_iri {
263 "http://www.w3.org/2001/XMLSchema#byte" if !(-128..=127).contains(&parsed_value) => {
264 return Err(OxirsError::Parse(format!(
265 "Byte value out of range: {parsed_value}. Must be between -128 and 127"
266 )));
267 }
268 "http://www.w3.org/2001/XMLSchema#short" if !(-32768..=32767).contains(&parsed_value) => {
269 return Err(OxirsError::Parse(format!(
270 "Short value out of range: {parsed_value}. Must be between -32768 and 32767"
271 )));
272 }
273 "http://www.w3.org/2001/XMLSchema#int"
274 if !(-2147483648..=2147483647).contains(&parsed_value) =>
275 {
276 return Err(OxirsError::Parse(format!(
277 "Int value out of range: {parsed_value}. Must be between -2147483648 and 2147483647"
278 )));
279 }
280 "http://www.w3.org/2001/XMLSchema#unsignedByte" if !(0..=255).contains(&parsed_value) => {
281 return Err(OxirsError::Parse(format!(
282 "Unsigned byte value out of range: {parsed_value}. Must be between 0 and 255"
283 )));
284 }
285 "http://www.w3.org/2001/XMLSchema#unsignedShort"
286 if !(0..=65535).contains(&parsed_value) =>
287 {
288 return Err(OxirsError::Parse(format!(
289 "Unsigned short value out of range: {parsed_value}. Must be between 0 and 65535"
290 )));
291 }
292 "http://www.w3.org/2001/XMLSchema#unsignedInt"
293 if !(0..=4294967295).contains(&parsed_value) =>
294 {
295 return Err(OxirsError::Parse(format!(
296 "Unsigned int value out of range: {parsed_value}. Must be between 0 and 4294967295"
297 )));
298 }
299 "http://www.w3.org/2001/XMLSchema#positiveInteger" if parsed_value <= 0 => {
300 return Err(OxirsError::Parse(format!(
301 "Positive integer must be greater than 0, got: {parsed_value}"
302 )));
303 }
304 "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" if parsed_value < 0 => {
305 return Err(OxirsError::Parse(format!(
306 "Non-negative integer must be >= 0, got: {parsed_value}"
307 )));
308 }
309 "http://www.w3.org/2001/XMLSchema#negativeInteger" if parsed_value >= 0 => {
310 return Err(OxirsError::Parse(format!(
311 "Negative integer must be less than 0, got: {parsed_value}"
312 )));
313 }
314 "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" if parsed_value > 0 => {
315 return Err(OxirsError::Parse(format!(
316 "Non-positive integer must be <= 0, got: {parsed_value}"
317 )));
318 }
319 _ => {} }
321
322 Ok(())
323}
324
325#[derive(Eq, PartialEq, Debug, Clone, Hash, PartialOrd, Ord)]
348#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
349pub struct Literal(LiteralContent);
350
351#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
352#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
353enum LiteralContent {
354 String(String),
355 LanguageTaggedString {
356 value: String,
357 language: String,
358 },
359 #[cfg(feature = "rdf-12")]
360 DirectionalLanguageTaggedString {
361 value: String,
362 language: String,
363 direction: BaseDirection,
364 },
365 TypedLiteral {
366 value: String,
367 datatype: NamedNode,
368 },
369}
370
371impl Literal {
372 #[inline]
374 pub fn new_simple_literal(value: impl Into<String>) -> Self {
375 Self(LiteralContent::String(value.into()))
376 }
377
378 #[inline]
380 pub fn new(value: impl Into<String>) -> Self {
381 Self::new_simple_literal(value)
382 }
383
384 #[inline]
386 pub fn new_typed_literal(value: impl Into<String>, datatype: impl Into<NamedNode>) -> Self {
387 let value = value.into();
388 let datatype = datatype.into();
389 Self(if datatype == *xsd::STRING {
390 LiteralContent::String(value)
391 } else {
392 LiteralContent::TypedLiteral { value, datatype }
393 })
394 }
395
396 #[inline]
398 pub fn new_typed(value: impl Into<String>, datatype: NamedNode) -> Self {
399 Self::new_typed_literal(value, datatype)
400 }
401
402 pub fn new_typed_validated(
404 value: impl Into<String>,
405 datatype: NamedNode,
406 ) -> Result<Self, OxirsError> {
407 let value = value.into();
408 validate_xsd_value(&value, datatype.as_str())?;
409 Ok(Literal::new_typed_literal(value, datatype))
410 }
411
412 #[inline]
414 pub fn new_language_tagged_literal(
415 value: impl Into<String>,
416 language: impl Into<String>,
417 ) -> Result<Self, LanguageTagParseError> {
418 let language = language.into().to_ascii_lowercase();
419 validate_language_tag(&language)?;
422 Ok(Self::new_language_tagged_literal_unchecked(value, language))
423 }
424
425 #[inline]
433 pub fn new_language_tagged_literal_unchecked(
434 value: impl Into<String>,
435 language: impl Into<String>,
436 ) -> Self {
437 Self(LiteralContent::LanguageTaggedString {
438 value: value.into(),
439 language: language.into(),
440 })
441 }
442
443 pub fn new_lang(
445 value: impl Into<String>,
446 language: impl Into<String>,
447 ) -> Result<Self, OxirsError> {
448 let result = Self::new_language_tagged_literal(value, language)?;
449 Ok(result)
450 }
451
452 #[cfg(feature = "rdf-12")]
454 #[inline]
455 pub fn new_directional_language_tagged_literal(
456 value: impl Into<String>,
457 language: impl Into<String>,
458 direction: impl Into<BaseDirection>,
459 ) -> Result<Self, LanguageTagParseError> {
460 let mut language = language.into();
461 language.make_ascii_lowercase();
462 validate_language_tag(&language)?;
463 Ok(Self::new_directional_language_tagged_literal_unchecked(
464 value, language, direction,
465 ))
466 }
467
468 #[cfg(feature = "rdf-12")]
476 #[inline]
477 pub fn new_directional_language_tagged_literal_unchecked(
478 value: impl Into<String>,
479 language: impl Into<String>,
480 direction: impl Into<BaseDirection>,
481 ) -> Self {
482 Self(LiteralContent::DirectionalLanguageTaggedString {
483 value: value.into(),
484 language: language.into(),
485 direction: direction.into(),
486 })
487 }
488
489 #[inline]
491 pub fn value(&self) -> &str {
492 self.as_ref().value()
493 }
494
495 #[inline]
500 pub fn language(&self) -> Option<&str> {
501 self.as_ref().language()
502 }
503
504 #[cfg(feature = "rdf-12")]
508 #[inline]
509 pub fn direction(&self) -> Option<BaseDirection> {
510 self.as_ref().direction()
511 }
512
513 #[inline]
518 pub fn datatype(&self) -> NamedNodeRef<'_> {
519 self.as_ref().datatype()
520 }
521
522 #[inline]
527 #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
528 pub fn is_plain(&self) -> bool {
529 #[allow(deprecated)]
530 self.as_ref().is_plain()
531 }
532
533 pub fn is_lang_string(&self) -> bool {
535 self.language().is_some()
536 }
537
538 pub fn is_typed(&self) -> bool {
540 matches!(&self.0, LiteralContent::TypedLiteral { .. })
541 }
542
543 #[inline]
544 pub fn as_ref(&self) -> LiteralRef<'_> {
545 LiteralRef(match &self.0 {
546 LiteralContent::String(value) => LiteralRefContent::String(value),
547 LiteralContent::LanguageTaggedString { value, language } => {
548 LiteralRefContent::LanguageTaggedString { value, language }
549 }
550 #[cfg(feature = "rdf-12")]
551 LiteralContent::DirectionalLanguageTaggedString {
552 value,
553 language,
554 direction,
555 } => LiteralRefContent::DirectionalLanguageTaggedString {
556 value,
557 language,
558 direction: *direction,
559 },
560 LiteralContent::TypedLiteral { value, datatype } => LiteralRefContent::TypedLiteral {
561 value,
562 datatype: NamedNodeRef::new_unchecked(datatype.as_str()),
563 },
564 })
565 }
566
567 #[inline]
569 pub fn destruct(self) -> (String, Option<NamedNode>, Option<String>) {
570 match self.0 {
571 LiteralContent::String(s) => (s, None, None),
572 LiteralContent::LanguageTaggedString { value, language } => {
573 (value, None, Some(language))
574 }
575 #[cfg(feature = "rdf-12")]
576 LiteralContent::DirectionalLanguageTaggedString {
577 value,
578 language,
579 direction: _,
580 } => (value, None, Some(language)),
581 LiteralContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
582 }
583 }
584
585 pub fn as_bool(&self) -> Option<bool> {
589 match self.value().to_lowercase().as_str() {
590 "true" | "1" => Some(true),
591 "false" | "0" => Some(false),
592 _ => None,
593 }
594 }
595
596 pub fn as_i64(&self) -> Option<i64> {
600 self.value().parse().ok()
601 }
602
603 pub fn as_i32(&self) -> Option<i32> {
605 self.value().parse().ok()
606 }
607
608 pub fn as_f64(&self) -> Option<f64> {
612 self.value().parse().ok()
613 }
614
615 pub fn as_f32(&self) -> Option<f32> {
617 self.value().parse().ok()
618 }
619
620 pub fn is_numeric(&self) -> bool {
622 match &self.0 {
623 LiteralContent::TypedLiteral { datatype, .. } => {
624 let dt_iri = datatype.as_str();
625 matches!(
626 dt_iri,
627 "http://www.w3.org/2001/XMLSchema#integer"
628 | "http://www.w3.org/2001/XMLSchema#decimal"
629 | "http://www.w3.org/2001/XMLSchema#double"
630 | "http://www.w3.org/2001/XMLSchema#float"
631 | "http://www.w3.org/2001/XMLSchema#long"
632 | "http://www.w3.org/2001/XMLSchema#int"
633 | "http://www.w3.org/2001/XMLSchema#short"
634 | "http://www.w3.org/2001/XMLSchema#byte"
635 | "http://www.w3.org/2001/XMLSchema#unsignedLong"
636 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
637 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
638 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
639 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
640 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
641 | "http://www.w3.org/2001/XMLSchema#negativeInteger"
642 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger"
643 )
644 }
645 _ => {
646 self.as_f64().is_some()
648 }
649 }
650 }
651
652 pub fn is_boolean(&self) -> bool {
654 match &self.0 {
655 LiteralContent::TypedLiteral { datatype, .. } => {
656 datatype.as_str() == "http://www.w3.org/2001/XMLSchema#boolean"
657 }
658 _ => self.as_bool().is_some(),
659 }
660 }
661
662 pub fn canonical_form(&self) -> Literal {
666 match &self.0 {
667 LiteralContent::TypedLiteral { value, datatype } => {
668 let dt_iri = datatype.as_str();
669 match dt_iri {
670 "http://www.w3.org/2001/XMLSchema#boolean" => {
671 if let Some(bool_val) = self.as_bool() {
672 let canonical_value = if bool_val { "true" } else { "false" };
673 return Literal::new_typed(canonical_value, datatype.clone());
674 }
675 }
676 "http://www.w3.org/2001/XMLSchema#integer"
677 | "http://www.w3.org/2001/XMLSchema#long"
678 | "http://www.w3.org/2001/XMLSchema#int"
679 | "http://www.w3.org/2001/XMLSchema#short"
680 | "http://www.w3.org/2001/XMLSchema#byte" => {
681 if let Some(int_val) = self.as_i64() {
682 return Literal::new_typed(int_val.to_string(), datatype.clone());
683 }
684 }
685 "http://www.w3.org/2001/XMLSchema#unsignedLong"
686 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
687 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
688 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
689 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
690 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => {
691 if let Some(int_val) = self.as_i64() {
692 if int_val >= 0 {
693 return Literal::new_typed(int_val.to_string(), datatype.clone());
694 }
695 }
696 }
697 "http://www.w3.org/2001/XMLSchema#negativeInteger"
698 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => {
699 if let Some(int_val) = self.as_i64() {
700 if int_val <= 0 {
701 return Literal::new_typed(int_val.to_string(), datatype.clone());
702 }
703 }
704 }
705 "http://www.w3.org/2001/XMLSchema#decimal" => {
706 if let Some(dec_val) = self.as_f64() {
707 let formatted = format!("{dec_val}");
709 if formatted.contains('.') {
710 let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
711 return Literal::new_typed(
712 if trimmed.is_empty() || trimmed == "-" {
713 "0"
714 } else {
715 trimmed
716 },
717 datatype.clone(),
718 );
719 } else {
720 return Literal::new_typed(
721 format!("{formatted}.0"),
722 datatype.clone(),
723 );
724 }
725 }
726 }
727 "http://www.w3.org/2001/XMLSchema#double"
728 | "http://www.w3.org/2001/XMLSchema#float" => {
729 if let Some(float_val) = self.as_f64() {
730 if float_val.is_infinite() {
732 return Literal::new_typed(
733 if float_val.is_sign_positive() {
734 "INF"
735 } else {
736 "-INF"
737 },
738 datatype.clone(),
739 );
740 } else if float_val.is_nan() {
741 return Literal::new_typed("NaN", datatype.clone());
742 } else {
743 let formatted = if float_val.abs() >= 1e6
745 || (float_val.abs() < 1e-3 && float_val != 0.0)
746 {
747 format!("{float_val:E}")
748 } else {
749 format!("{float_val}")
750 };
751 return Literal::new_typed(formatted, datatype.clone());
752 }
753 }
754 }
755 "http://www.w3.org/2001/XMLSchema#normalizedString" => {
756 let normalized = value.replace(['\t', '\n', '\r'], " ");
758 return Literal::new_typed(normalized, datatype.clone());
759 }
760 "http://www.w3.org/2001/XMLSchema#string" => {
761 }
763 "http://www.w3.org/2001/XMLSchema#token" => {
764 let normalized = value.split_whitespace().collect::<Vec<_>>().join(" ");
766 return Literal::new_typed(normalized, datatype.clone());
767 }
768 _ => {}
769 }
770 }
771 LiteralContent::LanguageTaggedString { value, language } => {
772 return Self(LiteralContent::LanguageTaggedString {
774 value: value.clone(),
775 language: language.clone(),
776 });
777 }
778 _ => {}
779 }
780 self.clone()
781 }
782
783 pub fn validate(&self) -> Result<(), OxirsError> {
785 match &self.0 {
786 LiteralContent::String(_) => Ok(()),
787 LiteralContent::LanguageTaggedString { language, .. } => {
788 validate_language_tag(language).map_err(Into::into)
789 }
790 #[cfg(feature = "rdf-12")]
791 LiteralContent::DirectionalLanguageTaggedString { language, .. } => {
792 validate_language_tag(language).map_err(Into::into)
793 }
794 LiteralContent::TypedLiteral { value, datatype } => {
795 validate_xsd_value(value, datatype.as_str())
796 }
797 }
798 }
799}
800
801impl fmt::Display for Literal {
802 #[inline]
803 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804 self.as_ref().fmt(f)
805 }
806}
807
808impl RdfTerm for Literal {
809 fn as_str(&self) -> &str {
810 self.value()
811 }
812
813 fn is_literal(&self) -> bool {
814 true
815 }
816}
817
818impl ObjectTerm for Literal {}
819
820#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
838pub struct LiteralRef<'a>(LiteralRefContent<'a>);
839
840#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
841enum LiteralRefContent<'a> {
842 String(&'a str),
843 LanguageTaggedString {
844 value: &'a str,
845 language: &'a str,
846 },
847 #[cfg(feature = "rdf-12")]
848 DirectionalLanguageTaggedString {
849 value: &'a str,
850 language: &'a str,
851 direction: BaseDirection,
852 },
853 TypedLiteral {
854 value: &'a str,
855 datatype: NamedNodeRef<'a>,
856 },
857}
858
859impl<'a> LiteralRef<'a> {
860 #[inline]
862 pub const fn new_simple_literal(value: &'a str) -> Self {
863 LiteralRef(LiteralRefContent::String(value))
864 }
865
866 #[inline]
868 pub const fn new(value: &'a str) -> Self {
869 Self::new_simple_literal(value)
870 }
871
872 #[inline]
874 pub fn new_typed_literal(value: &'a str, datatype: impl Into<NamedNodeRef<'a>>) -> Self {
875 let datatype = datatype.into();
876 LiteralRef(if datatype == xsd::STRING.as_ref() {
877 LiteralRefContent::String(value)
878 } else {
879 LiteralRefContent::TypedLiteral { value, datatype }
880 })
881 }
882
883 #[inline]
885 pub fn new_typed(value: &'a str, datatype: NamedNodeRef<'a>) -> Self {
886 Self::new_typed_literal(value, datatype)
887 }
888
889 #[inline]
897 pub const fn new_language_tagged_literal_unchecked(value: &'a str, language: &'a str) -> Self {
898 LiteralRef(LiteralRefContent::LanguageTaggedString { value, language })
899 }
900
901 #[inline]
903 pub const fn new_lang(value: &'a str, language: &'a str) -> Self {
904 Self::new_language_tagged_literal_unchecked(value, language)
905 }
906
907 #[cfg(feature = "rdf-12")]
915 #[inline]
916 pub const fn new_directional_language_tagged_literal_unchecked(
917 value: &'a str,
918 language: &'a str,
919 direction: BaseDirection,
920 ) -> Self {
921 LiteralRef(LiteralRefContent::DirectionalLanguageTaggedString {
922 value,
923 language,
924 direction,
925 })
926 }
927
928 #[inline]
930 pub const fn value(self) -> &'a str {
931 match self.0 {
932 LiteralRefContent::String(value)
933 | LiteralRefContent::LanguageTaggedString { value, .. }
934 | LiteralRefContent::TypedLiteral { value, .. } => value,
935 #[cfg(feature = "rdf-12")]
936 LiteralRefContent::DirectionalLanguageTaggedString { value, .. } => value,
937 }
938 }
939
940 #[inline]
945 pub const fn language(self) -> Option<&'a str> {
946 match self.0 {
947 LiteralRefContent::LanguageTaggedString { language, .. } => Some(language),
948 #[cfg(feature = "rdf-12")]
949 LiteralRefContent::DirectionalLanguageTaggedString { language, .. } => Some(language),
950 _ => None,
951 }
952 }
953
954 #[cfg(feature = "rdf-12")]
958 #[inline]
959 pub const fn direction(self) -> Option<BaseDirection> {
960 match self.0 {
961 LiteralRefContent::DirectionalLanguageTaggedString { direction, .. } => Some(direction),
962 _ => None,
963 }
964 }
965
966 #[inline]
971 pub fn datatype(self) -> NamedNodeRef<'a> {
972 match self.0 {
973 LiteralRefContent::String(_) => xsd::STRING.as_ref(),
974 LiteralRefContent::LanguageTaggedString { .. } => rdf::LANG_STRING.as_ref(),
975 #[cfg(feature = "rdf-12")]
976 LiteralRefContent::DirectionalLanguageTaggedString { .. } => {
977 rdf::DIR_LANG_STRING.as_ref()
978 }
979 LiteralRefContent::TypedLiteral { datatype, .. } => datatype,
980 }
981 }
982
983 #[inline]
988 #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
989 pub const fn is_plain(self) -> bool {
990 matches!(
991 self.0,
992 LiteralRefContent::String(_) | LiteralRefContent::LanguageTaggedString { .. }
993 )
994 }
995
996 #[inline]
997 pub fn into_owned(self) -> Literal {
998 Literal(match self.0 {
999 LiteralRefContent::String(value) => LiteralContent::String(value.to_owned()),
1000 LiteralRefContent::LanguageTaggedString { value, language } => {
1001 LiteralContent::LanguageTaggedString {
1002 value: value.to_owned(),
1003 language: language.to_owned(),
1004 }
1005 }
1006 #[cfg(feature = "rdf-12")]
1007 LiteralRefContent::DirectionalLanguageTaggedString {
1008 value,
1009 language,
1010 direction,
1011 } => LiteralContent::DirectionalLanguageTaggedString {
1012 value: value.to_owned(),
1013 language: language.to_owned(),
1014 direction,
1015 },
1016 LiteralRefContent::TypedLiteral { value, datatype } => LiteralContent::TypedLiteral {
1017 value: value.to_owned(),
1018 datatype: datatype.into_owned(),
1019 },
1020 })
1021 }
1022
1023 #[inline]
1025 pub fn to_owned(&self) -> Literal {
1026 self.into_owned()
1027 }
1028}
1029
1030impl fmt::Display for LiteralRef<'_> {
1031 #[inline]
1032 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1033 match self.0 {
1034 LiteralRefContent::String(value) => print_quoted_str(value, f),
1035 LiteralRefContent::LanguageTaggedString { value, language } => {
1036 print_quoted_str(value, f)?;
1037 write!(f, "@{language}")
1038 }
1039 #[cfg(feature = "rdf-12")]
1040 LiteralRefContent::DirectionalLanguageTaggedString {
1041 value,
1042 language,
1043 direction,
1044 } => {
1045 print_quoted_str(value, f)?;
1046 write!(f, "@{language}--{direction}")
1047 }
1048 LiteralRefContent::TypedLiteral { value, datatype } => {
1049 print_quoted_str(value, f)?;
1050 write!(f, "^^{datatype}")
1051 }
1052 }
1053 }
1054}
1055
1056impl<'a> RdfTerm for LiteralRef<'a> {
1057 fn as_str(&self) -> &str {
1058 self.value()
1059 }
1060
1061 fn is_literal(&self) -> bool {
1062 true
1063 }
1064}
1065
1066#[inline]
1068pub fn print_quoted_str(string: &str, f: &mut impl Write) -> fmt::Result {
1069 f.write_char('"')?;
1070 for c in string.chars() {
1071 match c {
1072 '\u{08}' => f.write_str("\\b"),
1073 '\t' => f.write_str("\\t"),
1074 '\n' => f.write_str("\\n"),
1075 '\u{0C}' => f.write_str("\\f"),
1076 '\r' => f.write_str("\\r"),
1077 '"' => f.write_str("\\\""),
1078 '\\' => f.write_str("\\\\"),
1079 '\0'..='\u{1F}' | '\u{7F}' => write!(f, "\\u{:04X}", u32::from(c)),
1080 _ => f.write_char(c),
1081 }?;
1082 }
1083 f.write_char('"')
1084}
1085
1086#[cfg(feature = "rdf-12")]
1088#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash, PartialOrd, Ord)]
1089#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1090pub enum BaseDirection {
1091 Ltr,
1093 Rtl,
1095}
1096
1097#[cfg(feature = "rdf-12")]
1098impl fmt::Display for BaseDirection {
1099 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1100 f.write_str(match self {
1101 Self::Ltr => "ltr",
1102 Self::Rtl => "rtl",
1103 })
1104 }
1105}
1106
1107impl<'a> From<&'a Literal> for LiteralRef<'a> {
1108 #[inline]
1109 fn from(node: &'a Literal) -> Self {
1110 node.as_ref()
1111 }
1112}
1113
1114impl<'a> From<LiteralRef<'a>> for Literal {
1115 #[inline]
1116 fn from(node: LiteralRef<'a>) -> Self {
1117 node.into_owned()
1118 }
1119}
1120
1121impl<'a> From<&'a str> for LiteralRef<'a> {
1122 #[inline]
1123 fn from(value: &'a str) -> Self {
1124 LiteralRef(LiteralRefContent::String(value))
1125 }
1126}
1127
1128impl PartialEq<Literal> for LiteralRef<'_> {
1129 #[inline]
1130 fn eq(&self, other: &Literal) -> bool {
1131 *self == other.as_ref()
1132 }
1133}
1134
1135impl PartialEq<LiteralRef<'_>> for Literal {
1136 #[inline]
1137 fn eq(&self, other: &LiteralRef<'_>) -> bool {
1138 self.as_ref() == *other
1139 }
1140}
1141
1142impl<'a> From<&'a str> for Literal {
1144 #[inline]
1145 fn from(value: &'a str) -> Self {
1146 Self(LiteralContent::String(value.into()))
1147 }
1148}
1149
1150impl From<String> for Literal {
1151 #[inline]
1152 fn from(value: String) -> Self {
1153 Self(LiteralContent::String(value))
1154 }
1155}
1156
1157impl<'a> From<Cow<'a, str>> for Literal {
1158 #[inline]
1159 fn from(value: Cow<'a, str>) -> Self {
1160 Self(LiteralContent::String(value.into()))
1161 }
1162}
1163
1164impl From<bool> for Literal {
1165 #[inline]
1166 fn from(value: bool) -> Self {
1167 Self(LiteralContent::TypedLiteral {
1168 value: value.to_string(),
1169 datatype: xsd::BOOLEAN.clone(),
1170 })
1171 }
1172}
1173
1174impl From<i128> for Literal {
1175 #[inline]
1176 fn from(value: i128) -> Self {
1177 Self(LiteralContent::TypedLiteral {
1178 value: value.to_string(),
1179 datatype: xsd::INTEGER.clone(),
1180 })
1181 }
1182}
1183
1184impl From<i64> for Literal {
1185 #[inline]
1186 fn from(value: i64) -> Self {
1187 Self(LiteralContent::TypedLiteral {
1188 value: value.to_string(),
1189 datatype: xsd::INTEGER.clone(),
1190 })
1191 }
1192}
1193
1194impl From<i32> for Literal {
1195 #[inline]
1196 fn from(value: i32) -> Self {
1197 Self(LiteralContent::TypedLiteral {
1198 value: value.to_string(),
1199 datatype: xsd::INTEGER.clone(),
1200 })
1201 }
1202}
1203
1204impl From<i16> for Literal {
1205 #[inline]
1206 fn from(value: i16) -> Self {
1207 Self(LiteralContent::TypedLiteral {
1208 value: value.to_string(),
1209 datatype: xsd::INTEGER.clone(),
1210 })
1211 }
1212}
1213
1214impl From<u64> for Literal {
1215 #[inline]
1216 fn from(value: u64) -> Self {
1217 Self(LiteralContent::TypedLiteral {
1218 value: value.to_string(),
1219 datatype: xsd::INTEGER.clone(),
1220 })
1221 }
1222}
1223
1224impl From<u32> for Literal {
1225 #[inline]
1226 fn from(value: u32) -> Self {
1227 Self(LiteralContent::TypedLiteral {
1228 value: value.to_string(),
1229 datatype: xsd::INTEGER.clone(),
1230 })
1231 }
1232}
1233
1234impl From<u16> for Literal {
1235 #[inline]
1236 fn from(value: u16) -> Self {
1237 Self(LiteralContent::TypedLiteral {
1238 value: value.to_string(),
1239 datatype: xsd::INTEGER.clone(),
1240 })
1241 }
1242}
1243
1244impl From<f32> for Literal {
1245 #[inline]
1246 fn from(value: f32) -> Self {
1247 Self(LiteralContent::TypedLiteral {
1248 value: if value == f32::INFINITY {
1249 "INF".to_owned()
1250 } else if value == f32::NEG_INFINITY {
1251 "-INF".to_owned()
1252 } else {
1253 value.to_string()
1254 },
1255 datatype: xsd::FLOAT.clone(),
1256 })
1257 }
1258}
1259
1260impl From<f64> for Literal {
1261 #[inline]
1262 fn from(value: f64) -> Self {
1263 Self(LiteralContent::TypedLiteral {
1264 value: if value == f64::INFINITY {
1265 "INF".to_owned()
1266 } else if value == f64::NEG_INFINITY {
1267 "-INF".to_owned()
1268 } else {
1269 value.to_string()
1270 },
1271 datatype: xsd::DOUBLE.clone(),
1272 })
1273 }
1274}
1275
1276pub mod xsd_literals {
1278 use super::*;
1279 use crate::vocab::xsd;
1280
1281 pub fn boolean_literal(value: bool) -> Literal {
1285 Literal::new_typed(value.to_string(), xsd::BOOLEAN.clone())
1286 }
1287
1288 pub fn integer_literal(value: i64) -> Literal {
1290 Literal::new_typed(value.to_string(), xsd::INTEGER.clone())
1291 }
1292
1293 pub fn decimal_literal(value: f64) -> Literal {
1295 Literal::new_typed(value.to_string(), xsd::DECIMAL.clone())
1296 }
1297
1298 pub fn double_literal(value: f64) -> Literal {
1300 Literal::new_typed(value.to_string(), xsd::DOUBLE.clone())
1301 }
1302
1303 pub fn string_literal(value: &str) -> Literal {
1305 Literal::new_typed(value, xsd::STRING.clone())
1306 }
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311 use super::*;
1312
1313 #[test]
1314 fn test_simple_literal_equality() {
1315 assert_eq!(
1316 Literal::new_simple_literal("foo"),
1317 Literal::new_typed_literal("foo", xsd::STRING.clone())
1318 );
1319 assert_eq!(
1320 Literal::new_simple_literal("foo"),
1321 LiteralRef::new_typed_literal("foo", xsd::STRING.as_ref())
1322 );
1323 assert_eq!(
1324 LiteralRef::new_simple_literal("foo"),
1325 Literal::new_typed_literal("foo", xsd::STRING.clone())
1326 );
1327 assert_eq!(
1328 LiteralRef::new_simple_literal("foo"),
1329 LiteralRef::new_typed_literal("foo", xsd::STRING.as_ref())
1330 );
1331 }
1332
1333 #[test]
1334 fn test_float_format() {
1335 assert_eq!("INF", Literal::from(f32::INFINITY).value());
1336 assert_eq!("INF", Literal::from(f64::INFINITY).value());
1337 assert_eq!("-INF", Literal::from(f32::NEG_INFINITY).value());
1338 assert_eq!("-INF", Literal::from(f64::NEG_INFINITY).value());
1339 assert_eq!("NaN", Literal::from(f32::NAN).value());
1340 assert_eq!("NaN", Literal::from(f64::NAN).value());
1341 }
1342
1343 #[test]
1344 fn test_plain_literal() {
1345 let literal = Literal::new("Hello");
1346 assert_eq!(literal.value(), "Hello");
1347 #[allow(deprecated)]
1348 {
1349 assert!(literal.is_plain());
1350 }
1351 assert!(!literal.is_lang_string());
1352 assert!(!literal.is_typed());
1353 assert_eq!(format!("{literal}"), "\"Hello\"");
1354 }
1355
1356 #[test]
1357 fn test_lang_literal() {
1358 let literal = Literal::new_lang("Hello", "en").expect("construction should succeed");
1359 assert_eq!(literal.value(), "Hello");
1360 assert_eq!(literal.language(), Some("en"));
1361 #[allow(deprecated)]
1362 {
1363 assert!(literal.is_plain());
1364 }
1365 assert!(literal.is_lang_string());
1366 assert!(!literal.is_typed());
1367 assert_eq!(format!("{literal}"), "\"Hello\"@en");
1368 }
1369
1370 #[test]
1371 fn test_typed_literal() {
1372 let literal = Literal::new_typed("42", xsd::INTEGER.clone());
1373 assert_eq!(literal.value(), "42");
1374 assert_eq!(
1375 literal.datatype().as_str(),
1376 "http://www.w3.org/2001/XMLSchema#integer"
1377 );
1378 #[allow(deprecated)]
1379 {
1380 assert!(!literal.is_plain());
1381 }
1382 assert!(!literal.is_lang_string());
1383 assert!(literal.is_typed());
1384 assert_eq!(
1385 format!("{literal}"),
1386 "\"42\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1387 );
1388 }
1389
1390 #[test]
1391 fn test_literal_ref() {
1392 let literal_ref = LiteralRef::new("test");
1393 assert_eq!(literal_ref.value(), "test");
1394
1395 let owned = literal_ref.to_owned();
1396 assert_eq!(owned.value(), "test");
1397 }
1398
1399 #[test]
1400 fn test_boolean_extraction() {
1401 let bool_literal = xsd_literals::boolean_literal(true);
1402 assert!(bool_literal.is_boolean());
1403 assert_eq!(bool_literal.as_bool(), Some(true));
1404
1405 let false_literal = Literal::new_typed("false", xsd::BOOLEAN.clone());
1406 assert_eq!(false_literal.as_bool(), Some(false));
1407
1408 let true_str = Literal::new("true");
1410 assert_eq!(true_str.as_bool(), Some(true));
1411
1412 let false_str = Literal::new("0");
1413 assert_eq!(false_str.as_bool(), Some(false));
1414 }
1415
1416 #[test]
1417 fn test_numeric_extraction() {
1418 let int_literal = xsd_literals::integer_literal(42);
1419 assert!(int_literal.is_numeric());
1420 assert_eq!(int_literal.as_i64(), Some(42));
1421 assert_eq!(int_literal.as_i32(), Some(42));
1422 assert_eq!(int_literal.as_f64(), Some(42.0));
1423
1424 let decimal_literal = xsd_literals::decimal_literal(3.25);
1425 assert!(decimal_literal.is_numeric());
1426 assert_eq!(decimal_literal.as_f64(), Some(3.25));
1427 assert_eq!(decimal_literal.as_f32(), Some(3.25_f32));
1428
1429 let untyped_num = Literal::new("123");
1431 assert!(untyped_num.is_numeric());
1432 assert_eq!(untyped_num.as_i64(), Some(123));
1433 }
1434
1435 #[test]
1436 fn test_canonical_form() {
1437 let bool_literal = Literal::new_typed("True", xsd::BOOLEAN.clone());
1439 let canonical = bool_literal.canonical_form();
1440 assert_eq!(canonical.value(), "true");
1441
1442 let int_literal = Literal::new_typed(" 42 ", xsd::INTEGER.clone());
1444 let canonical = int_literal.canonical_form();
1447 assert_eq!(
1448 canonical.datatype().as_str(),
1449 "http://www.w3.org/2001/XMLSchema#integer"
1450 );
1451
1452 let dec_literal = Literal::new_typed("3.140", xsd::DECIMAL.clone());
1454 let canonical = dec_literal.canonical_form();
1455 assert_eq!(canonical.value(), "3.14"); }
1457
1458 #[test]
1459 fn test_xsd_convenience_functions() {
1460 assert_eq!(xsd_literals::boolean_literal(true).value(), "true");
1462 assert_eq!(xsd_literals::integer_literal(123).value(), "123");
1463 assert_eq!(xsd_literals::decimal_literal(3.25).value(), "3.25");
1464 assert_eq!(xsd_literals::double_literal(2.71).value(), "2.71");
1465 assert_eq!(xsd_literals::string_literal("hello").value(), "hello");
1466
1467 assert_eq!(
1469 xsd_literals::boolean_literal(true).datatype().as_str(),
1470 "http://www.w3.org/2001/XMLSchema#boolean"
1471 );
1472 assert_eq!(
1473 xsd_literals::integer_literal(123).datatype().as_str(),
1474 "http://www.w3.org/2001/XMLSchema#integer"
1475 );
1476 }
1477
1478 #[test]
1479 fn test_numeric_type_detection() {
1480 let int_lit = Literal::new_typed("42", xsd::INTEGER.clone());
1482 assert!(int_lit.is_numeric());
1483
1484 let float_lit = Literal::new_typed("3.14", xsd::FLOAT.clone());
1485 assert!(float_lit.is_numeric());
1486
1487 let double_lit = Literal::new_typed("2.71", xsd::DOUBLE.clone());
1488 assert!(double_lit.is_numeric());
1489
1490 let string_lit = Literal::new_typed("hello", xsd::STRING.clone());
1492 assert!(!string_lit.is_numeric());
1493
1494 let bool_lit = Literal::new_typed("true", xsd::BOOLEAN.clone());
1495 assert!(!bool_lit.is_numeric());
1496 }
1497}