Expand description
purrdf-xsd — the native XSD value space for the RDF 1.2 query stack.
This is a pure-Rust, zero-runtime-dependency, wasm-clean leaf crate. It is
the drop-in replacement for the oxigraph-family oxsdatatypes, and the
foundation layer of the native SPARQL engine: the SPARQL evaluator evaluates
FILTER/ORDER BY over typed values, which this crate supplies. It is
deliberately decoupled from purrdf-core (no dependency in
either direction yet); the IR keeps literals lexical-verbatim (Constitution
C0.1) and this crate is the value layer that complements it.
§Two distinct identities (load-bearing — do not conflate)
A typed literal has TWO different notions of “equal”, and mixing them silently corrupts behavior:
- Term identity — RDF
sameTerm, which is the IR’s(lexical, datatype, language)tuple, NOT this crate’s value type. A consumer caches parsed values in aHashMap<TermId, XsdValue>keyed by thatTermId, soXsdValueitself needs noEq/Hash."1"^^xsd:integerand"01"^^xsd:integerare distinct term identities (different lexical forms) even though they share one value. - Value-space identity — SPARQL
=/<over the value (the free fnsvalue_eq/value_cmp). Here"1"^^xsd:integerand"1.0"^^xsd:decimalare equal (numeric promotion).
value_cmp returns Option<Ordering>: None means the values are genuinely
incomparable (NaN, indeterminate-timezone dateTime, the two-component partial
order of xsd:duration, or non-comparable cross-types) — a spec-mandated outcome,
never a degraded fallback. XsdValue therefore implements neither Eq/Hash
(term identity is the IR’s job, not this crate’s) nor PartialOrd/Ord (that
would re-introduce the conflation for BTreeMap); ordering is the free fn.
§XSD version: 1.1
purrdf-xsd targets the XSD 1.1 value spaces (W3C REC 2012-04-05). Two load-bearing consequences for the year lexical:
- Year
0000is permitted (XSD 1.1; it denotes 1 BCE). XSD 1.0 forbade it. - The year field must have at least 4 digits. A year field wider than 4 digits
must not have a leading zero — e.g.
00044-03-15and012345-01-01are invalid;12345-06-15and-12345-06-15are valid. Exactly 4 digits with a leading zero (0044,0000) are valid.
§XSD 1.0 compatibility
The crate stays honestly XSD 1.1 by default: parse/numeric::parse_float/
numeric::parse_double accept the XSD 1.1-only +INF spelling of positive
infinity for xsd:float/xsd:double. Spec-pinned consumers that reference XSD
1.0 (e.g. ShEx/SHACL/SPARQL conformance suites written against the 1.0 lexical
space) call parse_xsd10/parse_float_xsd10/parse_double_xsd10 instead,
which reject +INF (XSD 1.0 spells positive infinity only INF). This is an
opt-in function, not a Cargo feature — the kernel’s default behavior never
changes underneath a caller that did not ask for it.
§Datatype coverage
purrdf-xsd models — and value-compares — a superset of oxsdatatypes:
- numeric:
integer(i128), the twelve derived-integer facets (long/int/short/byte, theunsigned*family, andnonNegative/positive/nonPositive/negativeInteger, each range-checked),decimal(i128 mantissa + scale ≤ 18),float,double; boolean,string;- temporal:
dateTime/date/time,duration+dayTimeDuration/yearMonthDuration, and the gregorian familygYear/gMonth/gDay/gYearMonth/gMonthDay(tz-indeterminate partial order); - binary:
hexBinary/base64Binary(hand-rolled codecs — still zero-dep).
The derived-integer facets and the binary types are not modelled by
oxsdatatypes; the gregorian family matches it. Integer and decimal are
i128-bounded (decimal scale ≤ 18); lexicals beyond that domain hard-fail on
range rather than promoting to arbitrary precision.
§Hard-fail
Malformed lexical input is a hard error (XsdError), never a silent default.
Out-of-range integer/decimal lexicals fail rather than saturate (this crate is
i128-bounded — already exceeding oxsdatatypes’ i64).
§Examples
Parse lexical forms into the value space and compare across the numeric tower:
use std::cmp::Ordering;
use purrdf_xsd::{XsdDatatype, parse, value_cmp, value_eq};
// Parse maps a lexical form to the value it denotes.
let int = parse("42", XsdDatatype::Integer)?;
assert_eq!(int.canonical_lexical(), "42");
// SPARQL numeric promotion: `"42"^^xsd:integer = "42.0"^^xsd:decimal`.
let dec = parse("42.0", XsdDatatype::Decimal)?;
assert!(value_eq(&int, &dec));
// Ordering works across numeric types too.
let dbl = parse("2.5e1", XsdDatatype::Double)?;
assert_eq!(value_cmp(&dbl, &int), Some(Ordering::Less));
// Different value-space families are INCOMPARABLE (`None`), not "not equal".
let s = parse("42", XsdDatatype::String)?;
assert_eq!(value_cmp(&int, &s), None);
// Malformed or out-of-range lexicals hard-fail — never a silent default.
assert!(parse("4.2", XsdDatatype::Integer).is_err());
assert!(parse("300", XsdDatatype::Byte).is_err());Re-exports§
pub use binary::canonical_base64;pub use binary::canonical_hex;pub use binary::parse_base64;pub use binary::parse_binary;pub use binary::parse_hex;pub use datatype::XSD_NS;pub use datatype::XsdDatatype;pub use numeric::Decimal;pub use numeric::numeric_abs;pub use numeric::numeric_add;pub use numeric::numeric_ceil;pub use numeric::numeric_div;pub use numeric::numeric_floor;pub use numeric::numeric_mul;pub use numeric::numeric_round;pub use numeric::numeric_sub;pub use numeric::numeric_unary_minus;pub use numeric::numeric_unary_plus;pub use numeric::parse_double_xsd10;pub use numeric::parse_float_xsd10;pub use ops::effective_boolean_value;pub use ops::value_cmp;pub use ops::value_eq;pub use simple::normalize_whitespace_collapse;pub use simple::normalize_whitespace_replace;pub use temporal::datetime_epoch;pub use temporal::datetime_from_unix_seconds;pub use value::XsdError;pub use value::XsdValue;pub use value::parse;pub use value::parse_by_iri;pub use value::parse_xsd10;
Modules§
- binary
- Hand-rolled, zero-dependency codecs for
xsd:hexBinaryandxsd:base64Binary. - datatype
- The XSD datatype vocabulary this crate’s value space covers.
- numeric
- The XSD numeric value space:
integer,decimal,float,double, their lexical↔value parsing + canonical mapping, and the SPARQL numeric promotion lattice (integer ⊂ decimal ⊂ float ⊂ double) used for cross-type comparison. - ops
- The value-space operator surface: SPARQL
=/<and the effective boolean value. These are value-space operations (distinct from RDF term identity — see the crate docs). They are free functions, not trait impls, so they cannot be confused with the structuralEq/OrdaHashMap/BTreeMapwould use. - simple
- The simple (non-numeric, non-temporal) XSD value spaces:
booleanandstring. - temporal
- The XSD temporal value space:
dateTime,date,time,duration(with itsdayTimeDuration/yearMonthDurationsubtypes), and the Gregorian family (gYear,gMonth,gDay,gYearMonth,gMonthDay). - value
- The
XsdValuevalue type and theXsdErrorparse-failure type.