use super::*;
const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema#";
const RDF_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
fn xsd(name: &str) -> bool {
name.starts_with(XSD_NS)
}
fn xsd_local(datatype: &str) -> Option<&str> {
datatype.strip_prefix(XSD_NS)
}
fn is_string_datatype(datatype: &str) -> bool {
matches!(xsd_local(datatype), Some("string")) || datatype == RDF_LANG_STRING
}
fn is_integer_datatype(datatype: &str) -> bool {
matches!(
xsd_local(datatype),
Some(
"integer"
| "long"
| "int"
| "short"
| "byte"
| "nonNegativeInteger"
| "positiveInteger"
| "nonPositiveInteger"
| "negativeInteger"
| "unsignedLong"
| "unsignedInt"
| "unsignedShort"
| "unsignedByte"
)
)
}
fn is_float_datatype(datatype: &str) -> bool {
matches!(xsd_local(datatype), Some("float" | "double" | "decimal"))
}
fn is_xsd_integer_lexical(value: &str) -> bool {
let bytes = match value.as_bytes() {
[b'+' | b'-', rest @ ..] if !rest.is_empty() => rest,
rest if !rest.is_empty() => rest,
_ => return false,
};
bytes.iter().all(u8::is_ascii_digit)
}
fn is_xsd_float_lexical(value: &str) -> bool {
matches!(value, "INF" | "+INF" | "-INF" | "NaN") || value.parse::<f64>().is_ok()
}
fn is_xsd_datetime_lexical(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() < 19 {
return false;
}
let separators = [
(0..4, None),
(4..5, Some(b'-')),
(5..7, None),
(7..8, Some(b'-')),
(8..10, None),
(10..11, Some(b'T')),
(11..13, None),
(13..14, Some(b':')),
(14..16, None),
(16..17, Some(b':')),
(17..19, None),
];
for (range, expected) in separators {
let segment = &bytes[range];
match expected {
Some(sep) => {
if segment != [sep] {
return false;
}
}
None => {
if !segment.iter().all(u8::is_ascii_digit) {
return false;
}
}
}
}
let tail = &bytes[19..];
let tail = if let Some(stripped) = tail.strip_prefix(b".") {
let split = stripped
.iter()
.position(|b| !b.is_ascii_digit())
.unwrap_or(stripped.len());
if split == 0 {
return false;
}
&stripped[split..]
} else {
tail
};
match tail {
[] | [b'Z'] => true,
[b'+' | b'-', h1, h2, b':', m1, m2]
if h1.is_ascii_digit()
&& h2.is_ascii_digit()
&& m1.is_ascii_digit()
&& m2.is_ascii_digit() =>
{
true
}
_ => false,
}
}
pub(crate) fn value_matches_kind(term: &Term, value_kind: ValueKind) -> bool {
match value_kind {
ValueKind::Uri => term.as_iri().is_some(),
ValueKind::Url => term
.as_iri()
.is_some_and(|iri| is_namespace_url(iri.as_str())),
ValueKind::String => term
.as_literal()
.is_some_and(|literal| is_string_datatype(literal.datatype().as_str())),
ValueKind::Integer | ValueKind::Long => term.as_literal().is_some_and(|literal| {
let dt = literal.datatype().as_str();
(is_integer_datatype(dt) || is_string_datatype(dt))
&& is_xsd_integer_lexical(literal.value())
&& literal.value().parse::<i64>().is_ok()
}),
ValueKind::Float => term.as_literal().is_some_and(|literal| {
let dt = literal.datatype().as_str();
(is_float_datatype(dt) || is_string_datatype(dt))
&& is_xsd_float_lexical(literal.value())
}),
ValueKind::DateTime => term.as_literal().is_some_and(|literal| {
let dt = literal.datatype().as_str();
(xsd(dt) && xsd_local(dt) == Some("dateTime") || is_string_datatype(dt))
&& is_xsd_datetime_lexical(literal.value())
}),
}
}