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" => {
194 if !(-128..=127).contains(&parsed_value) {
195 return Err(OxirsError::Parse(format!(
196 "Byte value out of range: {parsed_value}. Must be between -128 and 127"
197 )));
198 }
199 }
200 "http://www.w3.org/2001/XMLSchema#short" => {
201 if !(-32768..=32767).contains(&parsed_value) {
202 return Err(OxirsError::Parse(format!(
203 "Short value out of range: {parsed_value}. Must be between -32768 and 32767"
204 )));
205 }
206 }
207 "http://www.w3.org/2001/XMLSchema#int" => {
208 if !(-2147483648..=2147483647).contains(&parsed_value) {
209 return Err(OxirsError::Parse(format!(
210 "Int value out of range: {parsed_value}. Must be between -2147483648 and 2147483647"
211 )));
212 }
213 }
214 "http://www.w3.org/2001/XMLSchema#unsignedByte" => {
215 if !(0..=255).contains(&parsed_value) {
216 return Err(OxirsError::Parse(format!(
217 "Unsigned byte value out of range: {parsed_value}. Must be between 0 and 255"
218 )));
219 }
220 }
221 "http://www.w3.org/2001/XMLSchema#unsignedShort" => {
222 if !(0..=65535).contains(&parsed_value) {
223 return Err(OxirsError::Parse(format!(
224 "Unsigned short value out of range: {parsed_value}. Must be between 0 and 65535"
225 )));
226 }
227 }
228 "http://www.w3.org/2001/XMLSchema#unsignedInt" => {
229 if !(0..=4294967295).contains(&parsed_value) {
230 return Err(OxirsError::Parse(format!(
231 "Unsigned int value out of range: {parsed_value}. Must be between 0 and 4294967295"
232 )));
233 }
234 }
235 "http://www.w3.org/2001/XMLSchema#positiveInteger" => {
236 if parsed_value <= 0 {
237 return Err(OxirsError::Parse(format!(
238 "Positive integer must be greater than 0, got: {parsed_value}"
239 )));
240 }
241 }
242 "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => {
243 if parsed_value < 0 {
244 return Err(OxirsError::Parse(format!(
245 "Non-negative integer must be >= 0, got: {parsed_value}"
246 )));
247 }
248 }
249 "http://www.w3.org/2001/XMLSchema#negativeInteger" => {
250 if parsed_value >= 0 {
251 return Err(OxirsError::Parse(format!(
252 "Negative integer must be less than 0, got: {parsed_value}"
253 )));
254 }
255 }
256 "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => {
257 if parsed_value > 0 {
258 return Err(OxirsError::Parse(format!(
259 "Non-positive integer must be <= 0, got: {parsed_value}"
260 )));
261 }
262 }
263 _ => {} }
265
266 Ok(())
267}
268
269fn validate_integer_range_oxs(integer: Integer, datatype_iri: &str) -> Result<(), OxirsError> {
271 let parsed_value: i64 = integer.to_string().parse().map_err(|_| {
273 OxirsError::Parse("Cannot convert integer to i64 for range validation".to_string())
274 })?;
275
276 match datatype_iri {
277 "http://www.w3.org/2001/XMLSchema#byte" => {
278 if !(-128..=127).contains(&parsed_value) {
279 return Err(OxirsError::Parse(format!(
280 "Byte value out of range: {parsed_value}. Must be between -128 and 127"
281 )));
282 }
283 }
284 "http://www.w3.org/2001/XMLSchema#short" => {
285 if !(-32768..=32767).contains(&parsed_value) {
286 return Err(OxirsError::Parse(format!(
287 "Short value out of range: {parsed_value}. Must be between -32768 and 32767"
288 )));
289 }
290 }
291 "http://www.w3.org/2001/XMLSchema#int" => {
292 if !(-2147483648..=2147483647).contains(&parsed_value) {
293 return Err(OxirsError::Parse(format!(
294 "Int value out of range: {parsed_value}. Must be between -2147483648 and 2147483647"
295 )));
296 }
297 }
298 "http://www.w3.org/2001/XMLSchema#unsignedByte" => {
299 if !(0..=255).contains(&parsed_value) {
300 return Err(OxirsError::Parse(format!(
301 "Unsigned byte value out of range: {parsed_value}. Must be between 0 and 255"
302 )));
303 }
304 }
305 "http://www.w3.org/2001/XMLSchema#unsignedShort" => {
306 if !(0..=65535).contains(&parsed_value) {
307 return Err(OxirsError::Parse(format!(
308 "Unsigned short value out of range: {parsed_value}. Must be between 0 and 65535"
309 )));
310 }
311 }
312 "http://www.w3.org/2001/XMLSchema#unsignedInt" => {
313 if !(0..=4294967295).contains(&parsed_value) {
314 return Err(OxirsError::Parse(format!(
315 "Unsigned int value out of range: {parsed_value}. Must be between 0 and 4294967295"
316 )));
317 }
318 }
319 "http://www.w3.org/2001/XMLSchema#positiveInteger" => {
320 if parsed_value <= 0 {
321 return Err(OxirsError::Parse(format!(
322 "Positive integer must be greater than 0, got: {parsed_value}"
323 )));
324 }
325 }
326 "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => {
327 if parsed_value < 0 {
328 return Err(OxirsError::Parse(format!(
329 "Non-negative integer must be >= 0, got: {parsed_value}"
330 )));
331 }
332 }
333 "http://www.w3.org/2001/XMLSchema#negativeInteger" => {
334 if parsed_value >= 0 {
335 return Err(OxirsError::Parse(format!(
336 "Negative integer must be less than 0, got: {parsed_value}"
337 )));
338 }
339 }
340 "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => {
341 if parsed_value > 0 {
342 return Err(OxirsError::Parse(format!(
343 "Non-positive integer must be <= 0, got: {parsed_value}"
344 )));
345 }
346 }
347 _ => {} }
349
350 Ok(())
351}
352
353#[derive(Eq, PartialEq, Debug, Clone, Hash, PartialOrd, Ord)]
376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
377pub struct Literal(LiteralContent);
378
379#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
380#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
381enum LiteralContent {
382 String(String),
383 LanguageTaggedString {
384 value: String,
385 language: String,
386 },
387 #[cfg(feature = "rdf-12")]
388 DirectionalLanguageTaggedString {
389 value: String,
390 language: String,
391 direction: BaseDirection,
392 },
393 TypedLiteral {
394 value: String,
395 datatype: NamedNode,
396 },
397}
398
399impl Literal {
400 #[inline]
402 pub fn new_simple_literal(value: impl Into<String>) -> Self {
403 Self(LiteralContent::String(value.into()))
404 }
405
406 #[inline]
408 pub fn new(value: impl Into<String>) -> Self {
409 Self::new_simple_literal(value)
410 }
411
412 #[inline]
414 pub fn new_typed_literal(value: impl Into<String>, datatype: impl Into<NamedNode>) -> Self {
415 let value = value.into();
416 let datatype = datatype.into();
417 Self(if datatype == *xsd::STRING {
418 LiteralContent::String(value)
419 } else {
420 LiteralContent::TypedLiteral { value, datatype }
421 })
422 }
423
424 #[inline]
426 pub fn new_typed(value: impl Into<String>, datatype: NamedNode) -> Self {
427 Self::new_typed_literal(value, datatype)
428 }
429
430 pub fn new_typed_validated(
432 value: impl Into<String>,
433 datatype: NamedNode,
434 ) -> Result<Self, OxirsError> {
435 let value = value.into();
436 validate_xsd_value(&value, datatype.as_str())?;
437 Ok(Literal::new_typed_literal(value, datatype))
438 }
439
440 #[inline]
442 pub fn new_language_tagged_literal(
443 value: impl Into<String>,
444 language: impl Into<String>,
445 ) -> Result<Self, LanguageTagParseError> {
446 let language = language.into();
447 validate_language_tag(&language)?;
449 Ok(Self::new_language_tagged_literal_unchecked(value, language))
450 }
451
452 #[inline]
460 pub fn new_language_tagged_literal_unchecked(
461 value: impl Into<String>,
462 language: impl Into<String>,
463 ) -> Self {
464 Self(LiteralContent::LanguageTaggedString {
465 value: value.into(),
466 language: language.into(),
467 })
468 }
469
470 pub fn new_lang(
472 value: impl Into<String>,
473 language: impl Into<String>,
474 ) -> Result<Self, OxirsError> {
475 let result = Self::new_language_tagged_literal(value, language)?;
476 Ok(result)
477 }
478
479 #[cfg(feature = "rdf-12")]
481 #[inline]
482 pub fn new_directional_language_tagged_literal(
483 value: impl Into<String>,
484 language: impl Into<String>,
485 direction: impl Into<BaseDirection>,
486 ) -> Result<Self, LanguageTagParseError> {
487 let mut language = language.into();
488 language.make_ascii_lowercase();
489 validate_language_tag(&language)?;
490 Ok(Self::new_directional_language_tagged_literal_unchecked(
491 value, language, direction,
492 ))
493 }
494
495 #[cfg(feature = "rdf-12")]
503 #[inline]
504 pub fn new_directional_language_tagged_literal_unchecked(
505 value: impl Into<String>,
506 language: impl Into<String>,
507 direction: impl Into<BaseDirection>,
508 ) -> Self {
509 Self(LiteralContent::DirectionalLanguageTaggedString {
510 value: value.into(),
511 language: language.into(),
512 direction: direction.into(),
513 })
514 }
515
516 #[inline]
518 pub fn value(&self) -> &str {
519 self.as_ref().value()
520 }
521
522 #[inline]
527 pub fn language(&self) -> Option<&str> {
528 self.as_ref().language()
529 }
530
531 #[cfg(feature = "rdf-12")]
535 #[inline]
536 pub fn direction(&self) -> Option<BaseDirection> {
537 self.as_ref().direction()
538 }
539
540 #[inline]
545 pub fn datatype(&self) -> NamedNodeRef<'_> {
546 self.as_ref().datatype()
547 }
548
549 #[inline]
554 #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
555 pub fn is_plain(&self) -> bool {
556 #[allow(deprecated)]
557 self.as_ref().is_plain()
558 }
559
560 pub fn is_lang_string(&self) -> bool {
562 self.language().is_some()
563 }
564
565 pub fn is_typed(&self) -> bool {
567 matches!(&self.0, LiteralContent::TypedLiteral { .. })
568 }
569
570 #[inline]
571 pub fn as_ref(&self) -> LiteralRef<'_> {
572 LiteralRef(match &self.0 {
573 LiteralContent::String(value) => LiteralRefContent::String(value),
574 LiteralContent::LanguageTaggedString { value, language } => {
575 LiteralRefContent::LanguageTaggedString { value, language }
576 }
577 #[cfg(feature = "rdf-12")]
578 LiteralContent::DirectionalLanguageTaggedString {
579 value,
580 language,
581 direction,
582 } => LiteralRefContent::DirectionalLanguageTaggedString {
583 value,
584 language,
585 direction: *direction,
586 },
587 LiteralContent::TypedLiteral { value, datatype } => LiteralRefContent::TypedLiteral {
588 value,
589 datatype: NamedNodeRef::new_unchecked(datatype.as_str()),
590 },
591 })
592 }
593
594 #[inline]
596 pub fn destruct(self) -> (String, Option<NamedNode>, Option<String>) {
597 match self.0 {
598 LiteralContent::String(s) => (s, None, None),
599 LiteralContent::LanguageTaggedString { value, language } => {
600 (value, None, Some(language))
601 }
602 #[cfg(feature = "rdf-12")]
603 LiteralContent::DirectionalLanguageTaggedString {
604 value,
605 language,
606 direction: _,
607 } => (value, None, Some(language)),
608 LiteralContent::TypedLiteral { value, datatype } => (value, Some(datatype), None),
609 }
610 }
611
612 pub fn as_bool(&self) -> Option<bool> {
616 match self.value().to_lowercase().as_str() {
617 "true" | "1" => Some(true),
618 "false" | "0" => Some(false),
619 _ => None,
620 }
621 }
622
623 pub fn as_i64(&self) -> Option<i64> {
627 self.value().parse().ok()
628 }
629
630 pub fn as_i32(&self) -> Option<i32> {
632 self.value().parse().ok()
633 }
634
635 pub fn as_f64(&self) -> Option<f64> {
639 self.value().parse().ok()
640 }
641
642 pub fn as_f32(&self) -> Option<f32> {
644 self.value().parse().ok()
645 }
646
647 pub fn is_numeric(&self) -> bool {
649 match &self.0 {
650 LiteralContent::TypedLiteral { datatype, .. } => {
651 let dt_iri = datatype.as_str();
652 matches!(
653 dt_iri,
654 "http://www.w3.org/2001/XMLSchema#integer"
655 | "http://www.w3.org/2001/XMLSchema#decimal"
656 | "http://www.w3.org/2001/XMLSchema#double"
657 | "http://www.w3.org/2001/XMLSchema#float"
658 | "http://www.w3.org/2001/XMLSchema#long"
659 | "http://www.w3.org/2001/XMLSchema#int"
660 | "http://www.w3.org/2001/XMLSchema#short"
661 | "http://www.w3.org/2001/XMLSchema#byte"
662 | "http://www.w3.org/2001/XMLSchema#unsignedLong"
663 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
664 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
665 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
666 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
667 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
668 | "http://www.w3.org/2001/XMLSchema#negativeInteger"
669 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger"
670 )
671 }
672 _ => {
673 self.as_f64().is_some()
675 }
676 }
677 }
678
679 pub fn is_boolean(&self) -> bool {
681 match &self.0 {
682 LiteralContent::TypedLiteral { datatype, .. } => {
683 datatype.as_str() == "http://www.w3.org/2001/XMLSchema#boolean"
684 }
685 _ => self.as_bool().is_some(),
686 }
687 }
688
689 pub fn canonical_form(&self) -> Literal {
693 match &self.0 {
694 LiteralContent::TypedLiteral { value, datatype } => {
695 let dt_iri = datatype.as_str();
696 match dt_iri {
697 "http://www.w3.org/2001/XMLSchema#boolean" => {
698 if let Some(bool_val) = self.as_bool() {
699 let canonical_value = if bool_val { "true" } else { "false" };
700 return Literal::new_typed(canonical_value, datatype.clone());
701 }
702 }
703 "http://www.w3.org/2001/XMLSchema#integer"
704 | "http://www.w3.org/2001/XMLSchema#long"
705 | "http://www.w3.org/2001/XMLSchema#int"
706 | "http://www.w3.org/2001/XMLSchema#short"
707 | "http://www.w3.org/2001/XMLSchema#byte" => {
708 if let Some(int_val) = self.as_i64() {
709 return Literal::new_typed(int_val.to_string(), datatype.clone());
710 }
711 }
712 "http://www.w3.org/2001/XMLSchema#unsignedLong"
713 | "http://www.w3.org/2001/XMLSchema#unsignedInt"
714 | "http://www.w3.org/2001/XMLSchema#unsignedShort"
715 | "http://www.w3.org/2001/XMLSchema#unsignedByte"
716 | "http://www.w3.org/2001/XMLSchema#positiveInteger"
717 | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => {
718 if let Some(int_val) = self.as_i64() {
719 if int_val >= 0 {
720 return Literal::new_typed(int_val.to_string(), datatype.clone());
721 }
722 }
723 }
724 "http://www.w3.org/2001/XMLSchema#negativeInteger"
725 | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => {
726 if let Some(int_val) = self.as_i64() {
727 if int_val <= 0 {
728 return Literal::new_typed(int_val.to_string(), datatype.clone());
729 }
730 }
731 }
732 "http://www.w3.org/2001/XMLSchema#decimal" => {
733 if let Some(dec_val) = self.as_f64() {
734 let formatted = format!("{dec_val}");
736 if formatted.contains('.') {
737 let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
738 return Literal::new_typed(
739 if trimmed.is_empty() || trimmed == "-" {
740 "0"
741 } else {
742 trimmed
743 },
744 datatype.clone(),
745 );
746 } else {
747 return Literal::new_typed(
748 format!("{formatted}.0"),
749 datatype.clone(),
750 );
751 }
752 }
753 }
754 "http://www.w3.org/2001/XMLSchema#double"
755 | "http://www.w3.org/2001/XMLSchema#float" => {
756 if let Some(float_val) = self.as_f64() {
757 if float_val.is_infinite() {
759 return Literal::new_typed(
760 if float_val.is_sign_positive() {
761 "INF"
762 } else {
763 "-INF"
764 },
765 datatype.clone(),
766 );
767 } else if float_val.is_nan() {
768 return Literal::new_typed("NaN", datatype.clone());
769 } else {
770 let formatted = if float_val.abs() >= 1e6
772 || (float_val.abs() < 1e-3 && float_val != 0.0)
773 {
774 format!("{float_val:E}")
775 } else {
776 format!("{float_val}")
777 };
778 return Literal::new_typed(formatted, datatype.clone());
779 }
780 }
781 }
782 "http://www.w3.org/2001/XMLSchema#string"
783 | "http://www.w3.org/2001/XMLSchema#normalizedString" => {
784 if dt_iri == "http://www.w3.org/2001/XMLSchema#normalizedString" {
786 let normalized = value.replace(['\t', '\n', '\r'], " ");
787 return Literal::new_typed(normalized, datatype.clone());
788 }
789 }
790 "http://www.w3.org/2001/XMLSchema#token" => {
791 let normalized = value.split_whitespace().collect::<Vec<_>>().join(" ");
793 return Literal::new_typed(normalized, datatype.clone());
794 }
795 _ => {}
796 }
797 }
798 LiteralContent::LanguageTaggedString { value, language } => {
799 return Self(LiteralContent::LanguageTaggedString {
801 value: value.clone(),
802 language: language.clone(),
803 });
804 }
805 _ => {}
806 }
807 self.clone()
808 }
809
810 pub fn validate(&self) -> Result<(), OxirsError> {
812 match &self.0 {
813 LiteralContent::String(_) => Ok(()),
814 LiteralContent::LanguageTaggedString { language, .. } => {
815 validate_language_tag(language).map_err(Into::into)
816 }
817 #[cfg(feature = "rdf-12")]
818 LiteralContent::DirectionalLanguageTaggedString { language, .. } => {
819 validate_language_tag(language).map_err(Into::into)
820 }
821 LiteralContent::TypedLiteral { value, datatype } => {
822 validate_xsd_value(value, datatype.as_str())
823 }
824 }
825 }
826}
827
828impl fmt::Display for Literal {
829 #[inline]
830 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
831 self.as_ref().fmt(f)
832 }
833}
834
835impl RdfTerm for Literal {
836 fn as_str(&self) -> &str {
837 self.value()
838 }
839
840 fn is_literal(&self) -> bool {
841 true
842 }
843}
844
845impl ObjectTerm for Literal {}
846
847#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
865pub struct LiteralRef<'a>(LiteralRefContent<'a>);
866
867#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
868enum LiteralRefContent<'a> {
869 String(&'a str),
870 LanguageTaggedString {
871 value: &'a str,
872 language: &'a str,
873 },
874 #[cfg(feature = "rdf-12")]
875 DirectionalLanguageTaggedString {
876 value: &'a str,
877 language: &'a str,
878 direction: BaseDirection,
879 },
880 TypedLiteral {
881 value: &'a str,
882 datatype: NamedNodeRef<'a>,
883 },
884}
885
886impl<'a> LiteralRef<'a> {
887 #[inline]
889 pub const fn new_simple_literal(value: &'a str) -> Self {
890 LiteralRef(LiteralRefContent::String(value))
891 }
892
893 #[inline]
895 pub const fn new(value: &'a str) -> Self {
896 Self::new_simple_literal(value)
897 }
898
899 #[inline]
901 pub fn new_typed_literal(value: &'a str, datatype: impl Into<NamedNodeRef<'a>>) -> Self {
902 let datatype = datatype.into();
903 LiteralRef(if datatype == xsd::STRING.as_ref() {
904 LiteralRefContent::String(value)
905 } else {
906 LiteralRefContent::TypedLiteral { value, datatype }
907 })
908 }
909
910 #[inline]
912 pub fn new_typed(value: &'a str, datatype: NamedNodeRef<'a>) -> Self {
913 Self::new_typed_literal(value, datatype)
914 }
915
916 #[inline]
924 pub const fn new_language_tagged_literal_unchecked(value: &'a str, language: &'a str) -> Self {
925 LiteralRef(LiteralRefContent::LanguageTaggedString { value, language })
926 }
927
928 #[inline]
930 pub const fn new_lang(value: &'a str, language: &'a str) -> Self {
931 Self::new_language_tagged_literal_unchecked(value, language)
932 }
933
934 #[cfg(feature = "rdf-12")]
942 #[inline]
943 pub const fn new_directional_language_tagged_literal_unchecked(
944 value: &'a str,
945 language: &'a str,
946 direction: BaseDirection,
947 ) -> Self {
948 LiteralRef(LiteralRefContent::DirectionalLanguageTaggedString {
949 value,
950 language,
951 direction,
952 })
953 }
954
955 #[inline]
957 pub const fn value(self) -> &'a str {
958 match self.0 {
959 LiteralRefContent::String(value)
960 | LiteralRefContent::LanguageTaggedString { value, .. }
961 | LiteralRefContent::TypedLiteral { value, .. } => value,
962 #[cfg(feature = "rdf-12")]
963 LiteralRefContent::DirectionalLanguageTaggedString { value, .. } => value,
964 }
965 }
966
967 #[inline]
972 pub const fn language(self) -> Option<&'a str> {
973 match self.0 {
974 LiteralRefContent::LanguageTaggedString { language, .. } => Some(language),
975 #[cfg(feature = "rdf-12")]
976 LiteralRefContent::DirectionalLanguageTaggedString { language, .. } => Some(language),
977 _ => None,
978 }
979 }
980
981 #[cfg(feature = "rdf-12")]
985 #[inline]
986 pub const fn direction(self) -> Option<BaseDirection> {
987 match self.0 {
988 LiteralRefContent::DirectionalLanguageTaggedString { direction, .. } => Some(direction),
989 _ => None,
990 }
991 }
992
993 #[inline]
998 pub fn datatype(self) -> NamedNodeRef<'a> {
999 match self.0 {
1000 LiteralRefContent::String(_) => xsd::STRING.as_ref(),
1001 LiteralRefContent::LanguageTaggedString { .. } => rdf::LANG_STRING.as_ref(),
1002 #[cfg(feature = "rdf-12")]
1003 LiteralRefContent::DirectionalLanguageTaggedString { .. } => {
1004 rdf::DIR_LANG_STRING.as_ref()
1005 }
1006 LiteralRefContent::TypedLiteral { datatype, .. } => datatype,
1007 }
1008 }
1009
1010 #[inline]
1015 #[deprecated(note = "Plain literal concept is removed in RDF 1.1", since = "0.3.0")]
1016 pub const fn is_plain(self) -> bool {
1017 matches!(
1018 self.0,
1019 LiteralRefContent::String(_) | LiteralRefContent::LanguageTaggedString { .. }
1020 )
1021 }
1022
1023 #[inline]
1024 pub fn into_owned(self) -> Literal {
1025 Literal(match self.0 {
1026 LiteralRefContent::String(value) => LiteralContent::String(value.to_owned()),
1027 LiteralRefContent::LanguageTaggedString { value, language } => {
1028 LiteralContent::LanguageTaggedString {
1029 value: value.to_owned(),
1030 language: language.to_owned(),
1031 }
1032 }
1033 #[cfg(feature = "rdf-12")]
1034 LiteralRefContent::DirectionalLanguageTaggedString {
1035 value,
1036 language,
1037 direction,
1038 } => LiteralContent::DirectionalLanguageTaggedString {
1039 value: value.to_owned(),
1040 language: language.to_owned(),
1041 direction,
1042 },
1043 LiteralRefContent::TypedLiteral { value, datatype } => LiteralContent::TypedLiteral {
1044 value: value.to_owned(),
1045 datatype: datatype.into_owned(),
1046 },
1047 })
1048 }
1049
1050 #[inline]
1052 pub fn to_owned(&self) -> Literal {
1053 self.into_owned()
1054 }
1055}
1056
1057impl fmt::Display for LiteralRef<'_> {
1058 #[inline]
1059 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1060 match self.0 {
1061 LiteralRefContent::String(value) => print_quoted_str(value, f),
1062 LiteralRefContent::LanguageTaggedString { value, language } => {
1063 print_quoted_str(value, f)?;
1064 write!(f, "@{language}")
1065 }
1066 #[cfg(feature = "rdf-12")]
1067 LiteralRefContent::DirectionalLanguageTaggedString {
1068 value,
1069 language,
1070 direction,
1071 } => {
1072 print_quoted_str(value, f)?;
1073 write!(f, "@{language}--{direction}")
1074 }
1075 LiteralRefContent::TypedLiteral { value, datatype } => {
1076 print_quoted_str(value, f)?;
1077 write!(f, "^^{datatype}")
1078 }
1079 }
1080 }
1081}
1082
1083impl<'a> RdfTerm for LiteralRef<'a> {
1084 fn as_str(&self) -> &str {
1085 self.value()
1086 }
1087
1088 fn is_literal(&self) -> bool {
1089 true
1090 }
1091}
1092
1093#[inline]
1095pub fn print_quoted_str(string: &str, f: &mut impl Write) -> fmt::Result {
1096 f.write_char('"')?;
1097 for c in string.chars() {
1098 match c {
1099 '\u{08}' => f.write_str("\\b"),
1100 '\t' => f.write_str("\\t"),
1101 '\n' => f.write_str("\\n"),
1102 '\u{0C}' => f.write_str("\\f"),
1103 '\r' => f.write_str("\\r"),
1104 '"' => f.write_str("\\\""),
1105 '\\' => f.write_str("\\\\"),
1106 '\0'..='\u{1F}' | '\u{7F}' => write!(f, "\\u{:04X}", u32::from(c)),
1107 _ => f.write_char(c),
1108 }?;
1109 }
1110 f.write_char('"')
1111}
1112
1113#[cfg(feature = "rdf-12")]
1115#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash, PartialOrd, Ord)]
1116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1117pub enum BaseDirection {
1118 Ltr,
1120 Rtl,
1122}
1123
1124#[cfg(feature = "rdf-12")]
1125impl fmt::Display for BaseDirection {
1126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1127 f.write_str(match self {
1128 Self::Ltr => "ltr",
1129 Self::Rtl => "rtl",
1130 })
1131 }
1132}
1133
1134impl<'a> From<&'a Literal> for LiteralRef<'a> {
1135 #[inline]
1136 fn from(node: &'a Literal) -> Self {
1137 node.as_ref()
1138 }
1139}
1140
1141impl<'a> From<LiteralRef<'a>> for Literal {
1142 #[inline]
1143 fn from(node: LiteralRef<'a>) -> Self {
1144 node.into_owned()
1145 }
1146}
1147
1148impl<'a> From<&'a str> for LiteralRef<'a> {
1149 #[inline]
1150 fn from(value: &'a str) -> Self {
1151 LiteralRef(LiteralRefContent::String(value))
1152 }
1153}
1154
1155impl PartialEq<Literal> for LiteralRef<'_> {
1156 #[inline]
1157 fn eq(&self, other: &Literal) -> bool {
1158 *self == other.as_ref()
1159 }
1160}
1161
1162impl PartialEq<LiteralRef<'_>> for Literal {
1163 #[inline]
1164 fn eq(&self, other: &LiteralRef<'_>) -> bool {
1165 self.as_ref() == *other
1166 }
1167}
1168
1169impl<'a> From<&'a str> for Literal {
1171 #[inline]
1172 fn from(value: &'a str) -> Self {
1173 Self(LiteralContent::String(value.into()))
1174 }
1175}
1176
1177impl From<String> for Literal {
1178 #[inline]
1179 fn from(value: String) -> Self {
1180 Self(LiteralContent::String(value))
1181 }
1182}
1183
1184impl<'a> From<Cow<'a, str>> for Literal {
1185 #[inline]
1186 fn from(value: Cow<'a, str>) -> Self {
1187 Self(LiteralContent::String(value.into()))
1188 }
1189}
1190
1191impl From<bool> for Literal {
1192 #[inline]
1193 fn from(value: bool) -> Self {
1194 Self(LiteralContent::TypedLiteral {
1195 value: value.to_string(),
1196 datatype: xsd::BOOLEAN.clone(),
1197 })
1198 }
1199}
1200
1201impl From<i128> for Literal {
1202 #[inline]
1203 fn from(value: i128) -> Self {
1204 Self(LiteralContent::TypedLiteral {
1205 value: value.to_string(),
1206 datatype: xsd::INTEGER.clone(),
1207 })
1208 }
1209}
1210
1211impl From<i64> for Literal {
1212 #[inline]
1213 fn from(value: i64) -> Self {
1214 Self(LiteralContent::TypedLiteral {
1215 value: value.to_string(),
1216 datatype: xsd::INTEGER.clone(),
1217 })
1218 }
1219}
1220
1221impl From<i32> for Literal {
1222 #[inline]
1223 fn from(value: i32) -> Self {
1224 Self(LiteralContent::TypedLiteral {
1225 value: value.to_string(),
1226 datatype: xsd::INTEGER.clone(),
1227 })
1228 }
1229}
1230
1231impl From<i16> for Literal {
1232 #[inline]
1233 fn from(value: i16) -> Self {
1234 Self(LiteralContent::TypedLiteral {
1235 value: value.to_string(),
1236 datatype: xsd::INTEGER.clone(),
1237 })
1238 }
1239}
1240
1241impl From<u64> for Literal {
1242 #[inline]
1243 fn from(value: u64) -> Self {
1244 Self(LiteralContent::TypedLiteral {
1245 value: value.to_string(),
1246 datatype: xsd::INTEGER.clone(),
1247 })
1248 }
1249}
1250
1251impl From<u32> for Literal {
1252 #[inline]
1253 fn from(value: u32) -> Self {
1254 Self(LiteralContent::TypedLiteral {
1255 value: value.to_string(),
1256 datatype: xsd::INTEGER.clone(),
1257 })
1258 }
1259}
1260
1261impl From<u16> for Literal {
1262 #[inline]
1263 fn from(value: u16) -> Self {
1264 Self(LiteralContent::TypedLiteral {
1265 value: value.to_string(),
1266 datatype: xsd::INTEGER.clone(),
1267 })
1268 }
1269}
1270
1271impl From<f32> for Literal {
1272 #[inline]
1273 fn from(value: f32) -> Self {
1274 Self(LiteralContent::TypedLiteral {
1275 value: if value == f32::INFINITY {
1276 "INF".to_owned()
1277 } else if value == f32::NEG_INFINITY {
1278 "-INF".to_owned()
1279 } else {
1280 value.to_string()
1281 },
1282 datatype: xsd::FLOAT.clone(),
1283 })
1284 }
1285}
1286
1287impl From<f64> for Literal {
1288 #[inline]
1289 fn from(value: f64) -> Self {
1290 Self(LiteralContent::TypedLiteral {
1291 value: if value == f64::INFINITY {
1292 "INF".to_owned()
1293 } else if value == f64::NEG_INFINITY {
1294 "-INF".to_owned()
1295 } else {
1296 value.to_string()
1297 },
1298 datatype: xsd::DOUBLE.clone(),
1299 })
1300 }
1301}
1302
1303pub mod xsd_literals {
1305 use super::*;
1306 use crate::vocab::xsd;
1307
1308 pub fn boolean_literal(value: bool) -> Literal {
1312 Literal::new_typed(value.to_string(), xsd::BOOLEAN.clone())
1313 }
1314
1315 pub fn integer_literal(value: i64) -> Literal {
1317 Literal::new_typed(value.to_string(), xsd::INTEGER.clone())
1318 }
1319
1320 pub fn decimal_literal(value: f64) -> Literal {
1322 Literal::new_typed(value.to_string(), xsd::DECIMAL.clone())
1323 }
1324
1325 pub fn double_literal(value: f64) -> Literal {
1327 Literal::new_typed(value.to_string(), xsd::DOUBLE.clone())
1328 }
1329
1330 pub fn string_literal(value: &str) -> Literal {
1332 Literal::new_typed(value, xsd::STRING.clone())
1333 }
1334}
1335
1336#[cfg(test)]
1337mod tests {
1338 use super::*;
1339
1340 #[test]
1341 fn test_simple_literal_equality() {
1342 assert_eq!(
1343 Literal::new_simple_literal("foo"),
1344 Literal::new_typed_literal("foo", xsd::STRING.clone())
1345 );
1346 assert_eq!(
1347 Literal::new_simple_literal("foo"),
1348 LiteralRef::new_typed_literal("foo", xsd::STRING.as_ref())
1349 );
1350 assert_eq!(
1351 LiteralRef::new_simple_literal("foo"),
1352 Literal::new_typed_literal("foo", xsd::STRING.clone())
1353 );
1354 assert_eq!(
1355 LiteralRef::new_simple_literal("foo"),
1356 LiteralRef::new_typed_literal("foo", xsd::STRING.as_ref())
1357 );
1358 }
1359
1360 #[test]
1361 fn test_float_format() {
1362 assert_eq!("INF", Literal::from(f32::INFINITY).value());
1363 assert_eq!("INF", Literal::from(f64::INFINITY).value());
1364 assert_eq!("-INF", Literal::from(f32::NEG_INFINITY).value());
1365 assert_eq!("-INF", Literal::from(f64::NEG_INFINITY).value());
1366 assert_eq!("NaN", Literal::from(f32::NAN).value());
1367 assert_eq!("NaN", Literal::from(f64::NAN).value());
1368 }
1369
1370 #[test]
1371 fn test_plain_literal() {
1372 let literal = Literal::new("Hello");
1373 assert_eq!(literal.value(), "Hello");
1374 #[allow(deprecated)]
1375 {
1376 assert!(literal.is_plain());
1377 }
1378 assert!(!literal.is_lang_string());
1379 assert!(!literal.is_typed());
1380 assert_eq!(format!("{literal}"), "\"Hello\"");
1381 }
1382
1383 #[test]
1384 fn test_lang_literal() {
1385 let literal = Literal::new_lang("Hello", "en").unwrap();
1386 assert_eq!(literal.value(), "Hello");
1387 assert_eq!(literal.language(), Some("en"));
1388 #[allow(deprecated)]
1389 {
1390 assert!(literal.is_plain());
1391 }
1392 assert!(literal.is_lang_string());
1393 assert!(!literal.is_typed());
1394 assert_eq!(format!("{literal}"), "\"Hello\"@en");
1395 }
1396
1397 #[test]
1398 fn test_typed_literal() {
1399 let literal = Literal::new_typed("42", xsd::INTEGER.clone());
1400 assert_eq!(literal.value(), "42");
1401 assert_eq!(
1402 literal.datatype().as_str(),
1403 "http://www.w3.org/2001/XMLSchema#integer"
1404 );
1405 #[allow(deprecated)]
1406 {
1407 assert!(!literal.is_plain());
1408 }
1409 assert!(!literal.is_lang_string());
1410 assert!(literal.is_typed());
1411 assert_eq!(
1412 format!("{literal}"),
1413 "\"42\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1414 );
1415 }
1416
1417 #[test]
1418 fn test_literal_ref() {
1419 let literal_ref = LiteralRef::new("test");
1420 assert_eq!(literal_ref.value(), "test");
1421
1422 let owned = literal_ref.to_owned();
1423 assert_eq!(owned.value(), "test");
1424 }
1425
1426 #[test]
1427 fn test_boolean_extraction() {
1428 let bool_literal = xsd_literals::boolean_literal(true);
1429 assert!(bool_literal.is_boolean());
1430 assert_eq!(bool_literal.as_bool(), Some(true));
1431
1432 let false_literal = Literal::new_typed("false", xsd::BOOLEAN.clone());
1433 assert_eq!(false_literal.as_bool(), Some(false));
1434
1435 let true_str = Literal::new("true");
1437 assert_eq!(true_str.as_bool(), Some(true));
1438
1439 let false_str = Literal::new("0");
1440 assert_eq!(false_str.as_bool(), Some(false));
1441 }
1442
1443 #[test]
1444 fn test_numeric_extraction() {
1445 let int_literal = xsd_literals::integer_literal(42);
1446 assert!(int_literal.is_numeric());
1447 assert_eq!(int_literal.as_i64(), Some(42));
1448 assert_eq!(int_literal.as_i32(), Some(42));
1449 assert_eq!(int_literal.as_f64(), Some(42.0));
1450
1451 let decimal_literal = xsd_literals::decimal_literal(3.25);
1452 assert!(decimal_literal.is_numeric());
1453 assert_eq!(decimal_literal.as_f64(), Some(3.25));
1454 assert_eq!(decimal_literal.as_f32(), Some(3.25_f32));
1455
1456 let untyped_num = Literal::new("123");
1458 assert!(untyped_num.is_numeric());
1459 assert_eq!(untyped_num.as_i64(), Some(123));
1460 }
1461
1462 #[test]
1463 fn test_canonical_form() {
1464 let bool_literal = Literal::new_typed("True", xsd::BOOLEAN.clone());
1466 let canonical = bool_literal.canonical_form();
1467 assert_eq!(canonical.value(), "true");
1468
1469 let int_literal = Literal::new_typed(" 42 ", xsd::INTEGER.clone());
1471 let canonical = int_literal.canonical_form();
1474 assert_eq!(
1475 canonical.datatype().as_str(),
1476 "http://www.w3.org/2001/XMLSchema#integer"
1477 );
1478
1479 let dec_literal = Literal::new_typed("3.140", xsd::DECIMAL.clone());
1481 let canonical = dec_literal.canonical_form();
1482 assert_eq!(canonical.value(), "3.14"); }
1484
1485 #[test]
1486 fn test_xsd_convenience_functions() {
1487 assert_eq!(xsd_literals::boolean_literal(true).value(), "true");
1489 assert_eq!(xsd_literals::integer_literal(123).value(), "123");
1490 assert_eq!(xsd_literals::decimal_literal(3.25).value(), "3.25");
1491 assert_eq!(xsd_literals::double_literal(2.71).value(), "2.71");
1492 assert_eq!(xsd_literals::string_literal("hello").value(), "hello");
1493
1494 assert_eq!(
1496 xsd_literals::boolean_literal(true).datatype().as_str(),
1497 "http://www.w3.org/2001/XMLSchema#boolean"
1498 );
1499 assert_eq!(
1500 xsd_literals::integer_literal(123).datatype().as_str(),
1501 "http://www.w3.org/2001/XMLSchema#integer"
1502 );
1503 }
1504
1505 #[test]
1506 fn test_numeric_type_detection() {
1507 let int_lit = Literal::new_typed("42", xsd::INTEGER.clone());
1509 assert!(int_lit.is_numeric());
1510
1511 let float_lit = Literal::new_typed("3.14", xsd::FLOAT.clone());
1512 assert!(float_lit.is_numeric());
1513
1514 let double_lit = Literal::new_typed("2.71", xsd::DOUBLE.clone());
1515 assert!(double_lit.is_numeric());
1516
1517 let string_lit = Literal::new_typed("hello", xsd::STRING.clone());
1519 assert!(!string_lit.is_numeric());
1520
1521 let bool_lit = Literal::new_typed("true", xsd::BOOLEAN.clone());
1522 assert!(!bool_lit.is_numeric());
1523 }
1524}