use std::fmt;
use num_bigint::BigInt;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use super::{PrimitiveTypeCode, XmlTypeCode};
use crate::ids::SimpleTypeKey;
use crate::namespace::qname::QualifiedName;
#[derive(Debug, Clone, PartialEq)]
pub struct XmlValue {
pub type_code: XmlTypeCode,
pub schema_type: Option<SimpleTypeKey>,
pub value: XmlValueKind,
}
impl XmlValue {
pub fn new(type_code: XmlTypeCode, value: XmlValueKind) -> Self {
Self {
type_code,
schema_type: None,
value,
}
}
pub fn with_schema_type(
type_code: XmlTypeCode,
schema_type: SimpleTypeKey,
value: XmlValueKind,
) -> Self {
Self {
type_code,
schema_type: Some(schema_type),
value,
}
}
pub fn untyped(s: impl Into<String>) -> Self {
Self {
type_code: XmlTypeCode::UntypedAtomic,
schema_type: None,
value: XmlValueKind::UntypedAtomic(s.into()),
}
}
pub fn string(s: impl Into<String>) -> Self {
Self {
type_code: XmlTypeCode::String,
schema_type: None,
value: XmlValueKind::Atomic(XmlAtomicValue::String(s.into())),
}
}
pub fn boolean(b: bool) -> Self {
Self {
type_code: XmlTypeCode::Boolean,
schema_type: None,
value: XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)),
}
}
pub fn decimal(d: Decimal) -> Self {
Self {
type_code: XmlTypeCode::Decimal,
schema_type: None,
value: XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)),
}
}
pub fn integer(i: BigInt) -> Self {
Self {
type_code: XmlTypeCode::Integer,
schema_type: None,
value: XmlValueKind::Atomic(XmlAtomicValue::Integer(i)),
}
}
pub fn float(f: f32) -> Self {
Self {
type_code: XmlTypeCode::Float,
schema_type: None,
value: XmlValueKind::Atomic(XmlAtomicValue::Float(f)),
}
}
pub fn double(d: f64) -> Self {
Self {
type_code: XmlTypeCode::Double,
schema_type: None,
value: XmlValueKind::Atomic(XmlAtomicValue::Double(d)),
}
}
pub fn is_atomic(&self) -> bool {
matches!(
self.value,
XmlValueKind::Atomic(_) | XmlValueKind::UntypedAtomic(_)
)
}
pub fn is_list(&self) -> bool {
matches!(self.value, XmlValueKind::List { .. })
}
pub fn is_union(&self) -> bool {
matches!(self.value, XmlValueKind::Union(_))
}
pub fn is_untyped(&self) -> bool {
matches!(self.value, XmlValueKind::UntypedAtomic(_))
}
pub fn primitive_type(&self) -> Option<PrimitiveTypeCode> {
PrimitiveTypeCode::from_type_code(self.type_code)
}
pub fn to_string_value(&self) -> String {
match &self.value {
XmlValueKind::Atomic(atom) => atom.to_string(),
XmlValueKind::List { items, .. } => items
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" "),
XmlValueKind::Union(inner) => inner.to_string_value(),
XmlValueKind::UntypedAtomic(s) => s.clone(),
}
}
pub fn as_boolean(&self) -> Option<bool> {
match &self.value {
XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => Some(*b),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match &self.value {
XmlValueKind::Atomic(XmlAtomicValue::String(s)) => Some(s),
XmlValueKind::UntypedAtomic(s) => Some(s),
_ => None,
}
}
pub fn as_decimal(&self) -> Option<Decimal> {
match &self.value {
XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => Some(*d),
XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => {
i.to_string().parse().ok()
}
_ => None,
}
}
pub fn as_integer(&self) -> Option<&BigInt> {
match &self.value {
XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => Some(i),
_ => None,
}
}
pub fn as_double(&self) -> Option<f64> {
match &self.value {
XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => Some(*d),
XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Some(*f as f64),
XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => d.to_string().parse().ok(),
XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => i.to_string().parse().ok(),
_ => None,
}
}
pub fn as_qname(&self) -> Option<&QualifiedName> {
match &self.value {
XmlValueKind::Atomic(XmlAtomicValue::QName(qn)) => Some(qn),
_ => None,
}
}
#[cfg(feature = "xsd11")]
pub fn to_xpath_value<N: crate::xpath::DomNavigator>(
&self,
item_schema_type: Option<SimpleTypeKey>,
) -> crate::xpath::XPathValue<N> {
use crate::xpath::iterator::XmlItem;
use crate::xpath::XPathValue;
match &self.value {
XmlValueKind::Atomic(_) | XmlValueKind::UntypedAtomic(_) => {
XPathValue::from_atomic(self.clone())
}
XmlValueKind::List { item_type, items } => {
let xml_items: Vec<XmlItem<N>> = items
.iter()
.map(|atom| {
let val = XmlValue {
type_code: atom.type_code(),
schema_type: item_schema_type,
value: XmlValueKind::Atomic(atom.clone()),
};
XmlItem::Atomic(val)
})
.collect();
let _ = item_type; XPathValue::from_sequence(xml_items)
}
XmlValueKind::Union(inner) => inner.to_xpath_value(item_schema_type),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum XmlValueKind {
Atomic(XmlAtomicValue),
List {
item_type: XmlTypeCode,
items: Vec<XmlAtomicValue>,
},
Union(Box<XmlValue>),
UntypedAtomic(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum XmlAtomicValue {
String(String),
Boolean(bool),
Decimal(Decimal),
Integer(BigInt),
Float(f32),
Double(f64),
DateTime(DateTimeValue),
Date(DateValue),
Time(TimeValue),
Duration(DurationValue),
GYearMonth(GYearMonthValue),
GYear(GYearValue),
GMonthDay(GMonthDayValue),
GDay(GDayValue),
GMonth(GMonthValue),
YearMonthDuration(YearMonthDurationValue),
DayTimeDuration(DayTimeDurationValue),
HexBinary(Vec<u8>),
Base64Binary(Vec<u8>),
AnyUri(String),
QName(QualifiedName),
Notation(QualifiedName),
}
impl XmlAtomicValue {
pub fn type_code(&self) -> XmlTypeCode {
match self {
Self::String(_) => XmlTypeCode::String,
Self::Boolean(_) => XmlTypeCode::Boolean,
Self::Decimal(_) => XmlTypeCode::Decimal,
Self::Integer(_) => XmlTypeCode::Integer,
Self::Float(_) => XmlTypeCode::Float,
Self::Double(_) => XmlTypeCode::Double,
Self::DateTime(_) => XmlTypeCode::DateTime,
Self::Date(_) => XmlTypeCode::Date,
Self::Time(_) => XmlTypeCode::Time,
Self::Duration(_) => XmlTypeCode::Duration,
Self::GYearMonth(_) => XmlTypeCode::GYearMonth,
Self::GYear(_) => XmlTypeCode::GYear,
Self::GMonthDay(_) => XmlTypeCode::GMonthDay,
Self::GDay(_) => XmlTypeCode::GDay,
Self::GMonth(_) => XmlTypeCode::GMonth,
Self::YearMonthDuration(_) => XmlTypeCode::YearMonthDuration,
Self::DayTimeDuration(_) => XmlTypeCode::DayTimeDuration,
Self::HexBinary(_) => XmlTypeCode::HexBinary,
Self::Base64Binary(_) => XmlTypeCode::Base64Binary,
Self::AnyUri(_) => XmlTypeCode::AnyUri,
Self::QName(_) => XmlTypeCode::QName,
Self::Notation(_) => XmlTypeCode::Notation,
}
}
pub fn primitive_type(&self) -> PrimitiveTypeCode {
match self {
Self::String(_) => PrimitiveTypeCode::String,
Self::Boolean(_) => PrimitiveTypeCode::Boolean,
Self::Decimal(_) | Self::Integer(_) => PrimitiveTypeCode::Decimal,
Self::Float(_) => PrimitiveTypeCode::Float,
Self::Double(_) => PrimitiveTypeCode::Double,
Self::DateTime(_) => PrimitiveTypeCode::DateTime,
Self::Date(_) => PrimitiveTypeCode::Date,
Self::Time(_) => PrimitiveTypeCode::Time,
Self::Duration(_) | Self::YearMonthDuration(_) | Self::DayTimeDuration(_) => {
PrimitiveTypeCode::Duration
}
Self::GYearMonth(_) => PrimitiveTypeCode::GYearMonth,
Self::GYear(_) => PrimitiveTypeCode::GYear,
Self::GMonthDay(_) => PrimitiveTypeCode::GMonthDay,
Self::GDay(_) => PrimitiveTypeCode::GDay,
Self::GMonth(_) => PrimitiveTypeCode::GMonth,
Self::HexBinary(_) => PrimitiveTypeCode::HexBinary,
Self::Base64Binary(_) => PrimitiveTypeCode::Base64Binary,
Self::AnyUri(_) => PrimitiveTypeCode::AnyUri,
Self::QName(_) => PrimitiveTypeCode::QName,
Self::Notation(_) => PrimitiveTypeCode::Notation,
}
}
}
impl fmt::Display for XmlAtomicValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(s) => write!(f, "{}", s),
Self::Boolean(b) => write!(f, "{}", if *b { "true" } else { "false" }),
Self::Decimal(d) => {
if d.fract().is_zero() {
write!(f, "{}", d.trunc())
} else {
write!(f, "{}", d.normalize())
}
}
Self::Integer(i) => write!(f, "{}", i),
Self::Float(v) => format_float(*v, f),
Self::Double(v) => format_double(*v, f),
Self::DateTime(dt) => write!(f, "{}", dt),
Self::Date(d) => write!(f, "{}", d),
Self::Time(t) => write!(f, "{}", t),
Self::Duration(d) => write!(f, "{}", d),
Self::GYearMonth(v) => write!(f, "{}", v),
Self::GYear(v) => write!(f, "{}", v),
Self::GMonthDay(v) => write!(f, "{}", v),
Self::GDay(v) => write!(f, "{}", v),
Self::GMonth(v) => write!(f, "{}", v),
Self::YearMonthDuration(v) => write!(f, "{}", v),
Self::DayTimeDuration(v) => write!(f, "{}", v),
Self::HexBinary(bytes) => {
write!(f, "{}", hex::encode_upper(bytes))
}
Self::Base64Binary(bytes) => {
use base64::Engine;
write!(
f,
"{}",
base64::engine::general_purpose::STANDARD.encode(bytes)
)
}
Self::AnyUri(uri) => write!(f, "{}", uri),
Self::QName(qn) => {
write!(f, "QName({:?}:{})", qn.namespace_uri, qn.local_name.0)
}
Self::Notation(n) => {
write!(f, "NOTATION({:?}:{})", n.namespace_uri, n.local_name.0)
}
}
}
}
fn fix_scientific_notation(s: &str) -> String {
if let Some(e_pos) = s.find('E') {
let mantissa = &s[..e_pos];
let exponent = &s[e_pos..]; if !mantissa.contains('.') {
format!("{}.0{}", mantissa, exponent)
} else {
s.to_string()
}
} else {
s.to_string()
}
}
fn format_float(v: f32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if v.is_nan() {
write!(f, "NaN")
} else if v.is_infinite() {
if v.is_sign_positive() {
write!(f, "INF")
} else {
write!(f, "-INF")
}
} else if v == 0.0 {
if v.is_sign_negative() {
write!(f, "-0")
} else {
write!(f, "0")
}
} else if v.abs() >= 1e-6 && v.abs() < 1e6 {
write!(f, "{}", v)
} else {
let s = format!("{:E}", v);
write!(f, "{}", fix_scientific_notation(&s))
}
}
fn format_double(v: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if v.is_nan() {
write!(f, "NaN")
} else if v.is_infinite() {
if v.is_sign_positive() {
write!(f, "INF")
} else {
write!(f, "-INF")
}
} else if v == 0.0 {
if v.is_sign_negative() {
write!(f, "-0")
} else {
write!(f, "0")
}
} else if v.abs() >= 1e-6 && v.abs() < 1e6 {
write!(f, "{}", v)
} else {
let s = format!("{:E}", v);
write!(f, "{}", fix_scientific_notation(&s))
}
}
fn format_year(year: i32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if year < 0 {
write!(f, "-{:04}", -year)
} else {
write!(f, "{:04}", year)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DateTimeValue {
pub year: i32,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: Decimal,
pub timezone: Option<TimezoneOffset>,
}
impl DateTimeValue {
fn to_comparable_instant(&self) -> Decimal {
let tz_minutes = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let days = date_to_days(self.year, self.month as i32, self.day as i32);
Decimal::from(days) * Decimal::from(86400)
+ Decimal::from(self.hour as i64) * Decimal::from(3600)
+ Decimal::from(self.minute as i64) * Decimal::from(60)
+ self.second
- Decimal::from(tz_minutes) * Decimal::from(60)
}
}
impl PartialOrd for DateTimeValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_comparable_instant()
.partial_cmp(&other.to_comparable_instant())
}
}
impl fmt::Display for DateTimeValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_year(self.year, f)?;
write!(
f,
"-{:02}-{:02}T{:02}:{:02}:",
self.month, self.day, self.hour, self.minute
)?;
format_seconds(self.second, f)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DateValue {
pub year: i32,
pub month: u8,
pub day: u8,
pub timezone: Option<TimezoneOffset>,
}
impl DateValue {
fn to_comparable_instant(&self) -> Decimal {
let tz_minutes = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let days = date_to_days(self.year, self.month as i32, self.day as i32);
Decimal::from(days) * Decimal::from(86400) - Decimal::from(tz_minutes) * Decimal::from(60)
}
}
impl PartialOrd for DateValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_comparable_instant()
.partial_cmp(&other.to_comparable_instant())
}
}
impl fmt::Display for DateValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_year(self.year, f)?;
write!(f, "-{:02}-{:02}", self.month, self.day)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TimeValue {
pub hour: u8,
pub minute: u8,
pub second: Decimal,
pub timezone: Option<TimezoneOffset>,
}
impl TimeValue {
fn to_comparable_seconds(&self) -> Decimal {
let tz_minutes = self.timezone.map_or(0i64, |tz| tz.0 as i64);
Decimal::from(self.hour as i64) * Decimal::from(3600)
+ Decimal::from(self.minute as i64) * Decimal::from(60)
+ self.second
- Decimal::from(tz_minutes) * Decimal::from(60)
}
}
impl PartialOrd for TimeValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_comparable_seconds()
.partial_cmp(&other.to_comparable_seconds())
}
}
impl fmt::Display for TimeValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02}:{:02}:", self.hour, self.minute)?;
format_seconds(self.second, f)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DurationValue {
pub negative: bool,
pub years: u32,
pub months: u32,
pub days: u32,
pub hours: u32,
pub minutes: u32,
pub seconds: Decimal,
}
impl DurationValue {
fn to_approx_total_seconds(&self) -> Decimal {
let month_secs = Decimal::from(2629746i64);
let total_months = Decimal::from(self.years as i64) * Decimal::from(12)
+ Decimal::from(self.months as i64);
let day_time_secs = Decimal::from(self.days as i64) * Decimal::from(86400)
+ Decimal::from(self.hours as i64) * Decimal::from(3600)
+ Decimal::from(self.minutes as i64) * Decimal::from(60)
+ self.seconds;
let total = total_months * month_secs + day_time_secs;
if self.negative {
-total
} else {
total
}
}
}
impl PartialOrd for DurationValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_approx_total_seconds()
.partial_cmp(&other.to_approx_total_seconds())
}
}
impl fmt::Display for DurationValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total_months = self.years * 12 + self.months;
let years = total_months / 12;
let months = total_months % 12;
let (days, hours, minutes, seconds) =
normalize_day_time(self.days, self.hours, self.minutes, self.seconds);
if self.negative {
write!(f, "-")?;
}
write!(f, "P")?;
if years > 0 {
write!(f, "{}Y", years)?;
}
if months > 0 {
write!(f, "{}M", months)?;
}
if days > 0 {
write!(f, "{}D", days)?;
}
if hours > 0 || minutes > 0 || !seconds.is_zero() {
write!(f, "T")?;
if hours > 0 {
write!(f, "{}H", hours)?;
}
if minutes > 0 {
write!(f, "{}M", minutes)?;
}
if !seconds.is_zero() {
format_duration_seconds(seconds, f)?;
write!(f, "S")?;
}
}
if years == 0 && months == 0 && days == 0 && hours == 0 && minutes == 0 && seconds.is_zero()
{
write!(f, "T0S")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct YearMonthDurationValue {
pub negative: bool,
pub years: u32,
pub months: u32,
}
impl fmt::Display for YearMonthDurationValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total_months = self.years * 12 + self.months;
let years = total_months / 12;
let months = total_months % 12;
if self.negative && (years > 0 || months > 0) {
write!(f, "-")?;
}
write!(f, "P")?;
if years > 0 {
write!(f, "{}Y", years)?;
}
if months > 0 {
write!(f, "{}M", months)?;
}
if years == 0 && months == 0 {
write!(f, "0M")?;
}
Ok(())
}
}
impl PartialOrd for YearMonthDurationValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let self_months = if self.negative {
-(self.years as i64 * 12 + self.months as i64)
} else {
self.years as i64 * 12 + self.months as i64
};
let other_months = if other.negative {
-(other.years as i64 * 12 + other.months as i64)
} else {
other.years as i64 * 12 + other.months as i64
};
self_months.partial_cmp(&other_months)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DayTimeDurationValue {
pub negative: bool,
pub days: u32,
pub hours: u32,
pub minutes: u32,
pub seconds: Decimal,
}
impl fmt::Display for DayTimeDurationValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (days, hours, minutes, seconds) =
normalize_day_time(self.days, self.hours, self.minutes, self.seconds);
if self.negative && (days > 0 || hours > 0 || minutes > 0 || !seconds.is_zero()) {
write!(f, "-")?;
}
write!(f, "P")?;
if days > 0 {
write!(f, "{}D", days)?;
}
if hours > 0 || minutes > 0 || !seconds.is_zero() {
write!(f, "T")?;
if hours > 0 {
write!(f, "{}H", hours)?;
}
if minutes > 0 {
write!(f, "{}M", minutes)?;
}
if !seconds.is_zero() {
format_duration_seconds(seconds, f)?;
write!(f, "S")?;
}
}
if days == 0 && hours == 0 && minutes == 0 && seconds.is_zero() {
write!(f, "T0S")?;
}
Ok(())
}
}
impl PartialOrd for DayTimeDurationValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let self_secs = {
let s = Decimal::from(self.days as i64) * Decimal::from(86400i64)
+ Decimal::from(self.hours as i64) * Decimal::from(3600i64)
+ Decimal::from(self.minutes as i64) * Decimal::from(60i64)
+ self.seconds;
if self.negative {
-s
} else {
s
}
};
let other_secs = {
let s = Decimal::from(other.days as i64) * Decimal::from(86400i64)
+ Decimal::from(other.hours as i64) * Decimal::from(3600i64)
+ Decimal::from(other.minutes as i64) * Decimal::from(60i64)
+ other.seconds;
if other.negative {
-s
} else {
s
}
};
self_secs.partial_cmp(&other_secs)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GYearMonthValue {
pub year: i32,
pub month: u8,
pub timezone: Option<TimezoneOffset>,
}
impl PartialOrd for GYearMonthValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
let d1 = date_to_days(self.year, self.month as i32, 1) * 1440 - tz1;
let d2 = date_to_days(other.year, other.month as i32, 1) * 1440 - tz2;
d1.partial_cmp(&d2)
}
}
impl fmt::Display for GYearMonthValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_year(self.year, f)?;
write!(f, "-{:02}", self.month)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GYearValue {
pub year: i32,
pub timezone: Option<TimezoneOffset>,
}
impl PartialOrd for GYearValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
let d1 = date_to_days(self.year, 1, 1) * 1440 - tz1;
let d2 = date_to_days(other.year, 1, 1) * 1440 - tz2;
d1.partial_cmp(&d2)
}
}
impl fmt::Display for GYearValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_year(self.year, f)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GMonthDayValue {
pub month: u8,
pub day: u8,
pub timezone: Option<TimezoneOffset>,
}
impl PartialOrd for GMonthDayValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
let d1 = date_to_days(2000, self.month as i32, self.day as i32) * 1440 - tz1;
let d2 = date_to_days(2000, other.month as i32, other.day as i32) * 1440 - tz2;
d1.partial_cmp(&d2)
}
}
impl fmt::Display for GMonthDayValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "--{:02}-{:02}", self.month, self.day)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GDayValue {
pub day: u8,
pub timezone: Option<TimezoneOffset>,
}
impl PartialOrd for GDayValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
let d1 = (self.day as i64) * 1440 - tz1;
let d2 = (other.day as i64) * 1440 - tz2;
d1.partial_cmp(&d2)
}
}
impl fmt::Display for GDayValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "---{:02}", self.day)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GMonthValue {
pub month: u8,
pub timezone: Option<TimezoneOffset>,
}
impl PartialOrd for GMonthValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
let d1 = (self.month as i64) * 1440 - tz1;
let d2 = (other.month as i64) * 1440 - tz2;
d1.partial_cmp(&d2)
}
}
impl fmt::Display for GMonthValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "--{:02}", self.month)?;
if let Some(tz) = &self.timezone {
write!(f, "{}", tz)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimezoneOffset(pub i16);
impl TimezoneOffset {
pub const UTC: Self = Self(0);
pub fn from_hm(hours: i8, minutes: i8) -> Self {
Self(hours as i16 * 60 + minutes as i16)
}
pub fn hours(&self) -> i8 {
(self.0 / 60) as i8
}
pub fn minutes(&self) -> i8 {
(self.0 % 60).abs() as i8
}
}
impl fmt::Display for TimezoneOffset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 == 0 {
write!(f, "Z")
} else {
let sign = if self.0 > 0 { '+' } else { '-' };
let hours = (self.0.abs() / 60) as u8;
let minutes = (self.0.abs() % 60) as u8;
write!(f, "{}{:02}:{:02}", sign, hours, minutes)
}
}
}
fn date_to_days(year: i32, month: i32, day: i32) -> i64 {
let (y, m) = if month <= 2 {
(year as i64 - 1, month as i64 + 12)
} else {
(year as i64, month as i64)
};
365 * y + y / 4 - y / 100 + y / 400 + (153 * (m - 3) + 2) / 5 + day as i64 - 307
}
fn normalize_day_time(
days: u32,
hours: u32,
minutes: u32,
seconds: Decimal,
) -> (u32, u32, u32, Decimal) {
let whole_secs = seconds.trunc();
let frac_secs = seconds - whole_secs;
let total_secs: u64 = whole_secs.to_u64().unwrap_or(0);
let mut mins = minutes as u64 + total_secs / 60;
let rem_secs = (total_secs % 60) as u32;
let mut hrs = hours as u64 + mins / 60;
mins %= 60;
let d = days as u64 + hrs / 24;
hrs %= 24;
let out_seconds = Decimal::from(rem_secs) + frac_secs;
(d as u32, hrs as u32, mins as u32, out_seconds)
}
fn format_seconds(s: Decimal, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if s.fract().is_zero() {
write!(f, "{:02}", s.trunc())
} else {
let formatted = format!("{}", s);
if let Some(dot_pos) = formatted.find('.') {
let int_part = &formatted[..dot_pos];
let frac_part = formatted[dot_pos + 1..].trim_end_matches('0');
if int_part.len() == 1 {
write!(f, "0{}.{}", int_part, frac_part)
} else {
write!(f, "{}.{}", int_part, frac_part)
}
} else {
write!(f, "{:02}", s)
}
}
}
fn format_duration_seconds(s: Decimal, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if s.fract().is_zero() {
write!(f, "{}", s.trunc())
} else {
let formatted = format!("{}", s);
if let Some(dot_pos) = formatted.find('.') {
let int_part = &formatted[..dot_pos];
let frac_part = formatted[dot_pos + 1..].trim_end_matches('0');
write!(f, "{}.{}", int_part, frac_part)
} else {
write!(f, "{}", s)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xml_value_string() {
let v = XmlValue::string("hello");
assert_eq!(v.type_code, XmlTypeCode::String);
assert!(v.is_atomic());
assert_eq!(v.to_string_value(), "hello");
assert_eq!(v.as_string(), Some("hello"));
}
#[test]
fn test_xml_value_boolean() {
let v = XmlValue::boolean(true);
assert_eq!(v.type_code, XmlTypeCode::Boolean);
assert_eq!(v.as_boolean(), Some(true));
assert_eq!(v.to_string_value(), "true");
let v = XmlValue::boolean(false);
assert_eq!(v.to_string_value(), "false");
}
#[test]
fn test_xml_value_decimal() {
let v = XmlValue::decimal(Decimal::new(12345, 2));
assert_eq!(v.type_code, XmlTypeCode::Decimal);
assert_eq!(v.as_decimal(), Some(Decimal::new(12345, 2)));
}
#[test]
fn test_xml_value_integer() {
let v = XmlValue::integer(BigInt::from(42));
assert_eq!(v.type_code, XmlTypeCode::Integer);
assert_eq!(v.as_integer(), Some(&BigInt::from(42)));
assert_eq!(v.to_string_value(), "42");
}
#[test]
fn test_xml_value_double() {
let v = XmlValue::double(2.5);
assert_eq!(v.type_code, XmlTypeCode::Double);
assert_eq!(v.as_double(), Some(2.5));
}
#[test]
fn test_xml_value_untyped() {
let v = XmlValue::untyped("raw text");
assert_eq!(v.type_code, XmlTypeCode::UntypedAtomic);
assert!(v.is_untyped());
assert_eq!(v.as_string(), Some("raw text"));
}
#[test]
fn test_xml_atomic_value_type_code() {
assert_eq!(
XmlAtomicValue::String("test".into()).type_code(),
XmlTypeCode::String
);
assert_eq!(
XmlAtomicValue::Boolean(true).type_code(),
XmlTypeCode::Boolean
);
assert_eq!(
XmlAtomicValue::Integer(BigInt::from(1)).type_code(),
XmlTypeCode::Integer
);
}
#[test]
fn test_timezone_display() {
assert_eq!(TimezoneOffset::UTC.to_string(), "Z");
assert_eq!(TimezoneOffset::from_hm(5, 30).to_string(), "+05:30");
assert_eq!(TimezoneOffset::from_hm(-8, 0).to_string(), "-08:00");
}
#[test]
fn test_date_display() {
let d = DateValue {
year: 2024,
month: 3,
day: 15,
timezone: Some(TimezoneOffset::UTC),
};
assert_eq!(d.to_string(), "2024-03-15Z");
}
#[test]
fn test_duration_display() {
let d = DurationValue {
negative: false,
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
seconds: Decimal::new(65, 1), };
assert!(d.to_string().starts_with("P1Y2M3DT4H5M"));
assert!(d.to_string().contains("6.5S"));
}
#[test]
fn test_float_special_values() {
assert_eq!(format!("{}", XmlAtomicValue::Float(f32::INFINITY)), "INF");
assert_eq!(
format!("{}", XmlAtomicValue::Float(f32::NEG_INFINITY)),
"-INF"
);
assert_eq!(format!("{}", XmlAtomicValue::Float(f32::NAN)), "NaN");
}
#[test]
fn test_hex_binary_display() {
let v = XmlAtomicValue::HexBinary(vec![0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(format!("{}", v), "DEADBEEF");
}
#[test]
fn test_base64_binary_display() {
let v = XmlAtomicValue::Base64Binary(b"Hello".to_vec());
assert_eq!(format!("{}", v), "SGVsbG8=");
}
}